From c4ab013689058f87b7b1230eddc7107afb13f801 Mon Sep 17 00:00:00 2001 From: Fahri Gedik Date: Tue, 7 Oct 2025 17:23:14 +0300 Subject: [PATCH 01/27] Add AI config schematic for Angular projects Introduces a new 'ai-config' schematic that generates configuration files for various AI tools (Claude, Copilot, Cursor, Gemini, Junie, Windsurf) to guide code generation and best practices in Angular and ABP Framework projects. Includes schematic implementation, schema, templates for each tool, and updates to collection.json and build scripts. --- .../packages/schematics/src/collection.json | 6 +- .../ai-config/files/claude/.claude/CLAUDE.md | 106 +++ .../copilot/.github/copilot-instructions.md | 158 +++++ .../files/cursor/.cursor/rules/cursor.mdc | 271 ++++++++ .../ai-config/files/gemini/.gemini/GEMINI.md | 240 +++++++ .../files/junie/.junie/guidelines.md | 352 ++++++++++ .../windsurf/.windsurf/rules/guidelines.md | 652 ++++++++++++++++++ .../src/commands/ai-config/index.ts | 104 +++ .../src/commands/ai-config/model.ts | 12 + .../src/commands/ai-config/schema.json | 44 ++ npm/ng-packs/scripts/build-schematics.ts | 2 + 11 files changed, 1946 insertions(+), 1 deletion(-) create mode 100644 npm/ng-packs/packages/schematics/src/commands/ai-config/files/claude/.claude/CLAUDE.md create mode 100644 npm/ng-packs/packages/schematics/src/commands/ai-config/files/copilot/.github/copilot-instructions.md create mode 100644 npm/ng-packs/packages/schematics/src/commands/ai-config/files/cursor/.cursor/rules/cursor.mdc create mode 100644 npm/ng-packs/packages/schematics/src/commands/ai-config/files/gemini/.gemini/GEMINI.md create mode 100644 npm/ng-packs/packages/schematics/src/commands/ai-config/files/junie/.junie/guidelines.md create mode 100644 npm/ng-packs/packages/schematics/src/commands/ai-config/files/windsurf/.windsurf/rules/guidelines.md create mode 100644 npm/ng-packs/packages/schematics/src/commands/ai-config/index.ts create mode 100644 npm/ng-packs/packages/schematics/src/commands/ai-config/model.ts create mode 100644 npm/ng-packs/packages/schematics/src/commands/ai-config/schema.json diff --git a/npm/ng-packs/packages/schematics/src/collection.json b/npm/ng-packs/packages/schematics/src/collection.json index 16f148d78a..c40d7dac14 100644 --- a/npm/ng-packs/packages/schematics/src/collection.json +++ b/npm/ng-packs/packages/schematics/src/collection.json @@ -34,7 +34,11 @@ "description": "ABP Change Styles of Theme Schematics", "factory": "./commands/change-theme", "schema": "./commands/change-theme/schema.json" - + }, + "ai-config": { + "description": "Generates AI configuration files for Angular projects", + "factory": "./commands/ai-config", + "schema": "./commands/ai-config/schema.json" } } } diff --git a/npm/ng-packs/packages/schematics/src/commands/ai-config/files/claude/.claude/CLAUDE.md b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/claude/.claude/CLAUDE.md new file mode 100644 index 0000000000..66c1a515ee --- /dev/null +++ b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/claude/.claude/CLAUDE.md @@ -0,0 +1,106 @@ +# Angular & ABP Framework Development Rules for Claude + +## Project Context +This is an Angular application built with the ABP Framework. Follow these rules to generate high-quality, maintainable code. + +## Angular Best Practices + +### Component Development +- Use OnPush change detection strategy by default +- Implement OnDestroy and unsubscribe from observables +- Keep components focused on presentation logic +- Use smart/dumb component pattern +- Prefer standalone components in new code +- Use proper TypeScript typing, avoid `any` + +### Service Development +- Make services injectable with `providedIn: 'root'` when possible +- Use dependency injection properly +- Handle errors appropriately with RxJS operators +- Return observables for async operations +- Keep services focused on single responsibility + +### RxJS Best Practices +- Use proper operators: `switchMap`, `mergeMap`, `concatMap`, `exhaustMap` +- Always unsubscribe using `takeUntil`, `take`, or async pipe +- Avoid nested subscriptions +- Use `shareReplay` for shared streams +- Handle errors with `catchError` + +### Template Best Practices +- Use async pipe for observables +- Avoid complex logic in templates +- Use trackBy with *ngFor +- Use proper change detection +- Follow accessibility guidelines (ARIA attributes) + +## ABP Framework Specific Rules + +### Module Structure +- Follow ABP's modular architecture +- Use feature modules appropriately +- Leverage ABP's configuration services +- Use ABP's localization system + +### State Management +- Use ABP's state management patterns +- Leverage NGXS for complex state +- Use ABP's store decorators properly + +### API Integration +- Use ABP's generated proxy services +- Follow ABP's REST API conventions +- Handle ABP's error responses +- Use ABP's permission system + +### Localization +- Use ABP's localization pipes and services +- Define localization keys in resource files +- Follow ABP's localization naming conventions + +### Authentication & Authorization +- Use ABP's auth guards +- Leverage permission directives +- Handle ABP's multi-tenancy + +## Code Style +- Follow Angular style guide +- Use meaningful variable and function names +- Add JSDoc comments for complex logic +- Keep functions small and focused +- Use TypeScript strict mode +- Format code with Prettier + +## Testing +- Write unit tests for services and components +- Use Jest for testing +- Mock dependencies properly +- Aim for good test coverage +- Test error scenarios + +## File Organization +- Follow Nx workspace conventions +- Use proper folder structure +- Group related files together +- Use barrel exports (index.ts) + +## Performance +- Lazy load feature modules +- Use OnPush change detection +- Optimize bundle size +- Use production builds +- Implement proper caching strategies + +## Security +- Sanitize user inputs +- Use proper Content Security Policy +- Follow OWASP guidelines +- Validate data on client and server + +## Git Practices +- Write meaningful commit messages +- Keep commits atomic +- Follow conventional commits +- Create feature branches + +When generating code, always consider these rules and the context of the ABP Framework and Angular ecosystem. diff --git a/npm/ng-packs/packages/schematics/src/commands/ai-config/files/copilot/.github/copilot-instructions.md b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/copilot/.github/copilot-instructions.md new file mode 100644 index 0000000000..f39facbae6 --- /dev/null +++ b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/copilot/.github/copilot-instructions.md @@ -0,0 +1,158 @@ +# GitHub Copilot Instructions for Angular & ABP Framework + +You are an expert Angular and ABP Framework developer. Follow these guidelines when generating code suggestions. + +## Angular Development Standards + +### Components +- Create components with OnPush change detection strategy +- Implement lifecycle hooks properly (OnInit, OnDestroy) +- Use standalone components for new features +- Follow smart/dumb component pattern +- Unsubscribe from observables using takeUntil pattern or async pipe + +Example: +```typescript +@Component({ + selector: 'app-example', + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [CommonModule, ReactiveFormsModule] +}) +export class ExampleComponent implements OnInit, OnDestroy { + private destroy$ = new Subject(); + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } +} +``` + +### Services +- Use providedIn: 'root' for singleton services +- Return Observables for async operations +- Handle errors with proper RxJS operators +- Keep services focused on single responsibility + +Example: +```typescript +@Injectable({ providedIn: 'root' }) +export class DataService { + constructor(private http: HttpClient) {} + + getData(): Observable { + return this.http.get('/api/data').pipe( + catchError(this.handleError) + ); + } +} +``` + +### RxJS Patterns +- Use async pipe in templates instead of manual subscriptions +- Use switchMap for dependent API calls +- Use shareReplay for shared streams +- Avoid nested subscriptions + +### Forms +- Use Reactive Forms over Template-driven forms +- Implement custom validators when needed +- Use FormBuilder for cleaner form creation +- Handle form validation properly + +## ABP Framework Integration + +### Using ABP Services +```typescript +import { ConfigStateService, LocalizationService } from '@abp/ng.core'; + +constructor( + private config: ConfigStateService, + private localization: LocalizationService +) {} +``` + +### Localization +```typescript +// In component +this.localization.instant('::LocalizationKey') + +// In template +{{ '::LocalizationKey' | abpLocalization }} +``` + +### Permissions +```typescript +// In template + + +// In component +if (this.config.getGrantedPolicy('MyApp.MyPermission')) { + // do something +} +``` + +### API Proxy Integration +- Use ABP's generated proxy services +- Don't create manual HTTP calls for ABP APIs +- Follow ABP's DTOs and service patterns + +### State Management with NGXS +```typescript +@State({ + name: 'MyState', + defaults: { items: [] } +}) +@Injectable() +export class MyState { + @Action(GetItems) + getItems(ctx: StateContext) { + return this.service.getItems().pipe( + tap(items => ctx.patchState({ items })) + ); + } +} +``` + +## Code Quality Standards +- Use TypeScript strict mode +- Avoid using `any` type +- Implement proper error handling +- Write meaningful variable and function names +- Add comments for complex logic +- Follow SOLID principles + +## Testing +- Write unit tests for components and services +- Use Jest testing framework +- Mock dependencies properly +- Test both success and error scenarios + +## File Structure +Follow Nx workspace structure: +``` +libs/ + feature-name/ + src/ + lib/ + components/ + services/ + models/ + state/ +``` + +## Performance +- Use OnPush change detection +- Lazy load feature modules +- Use trackBy in *ngFor +- Optimize bundle size +- Avoid memory leaks + +## Security +- Never commit sensitive data +- Sanitize user inputs +- Use proper authentication guards +- Follow ABP's security patterns + +Always prioritize code maintainability, readability, and following Angular and ABP best practices. diff --git a/npm/ng-packs/packages/schematics/src/commands/ai-config/files/cursor/.cursor/rules/cursor.mdc b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/cursor/.cursor/rules/cursor.mdc new file mode 100644 index 0000000000..fbcf65e2fa --- /dev/null +++ b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/cursor/.cursor/rules/cursor.mdc @@ -0,0 +1,271 @@ +# Cursor AI Rules for Angular & ABP Framework Development + +## Core Principles +- Write clean, maintainable, and testable code +- Follow Angular style guide and ABP conventions +- Use TypeScript strict mode +- Prioritize code readability over cleverness + +## Angular Component Guidelines + +### Component Structure +```typescript +import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Subject, takeUntil } from 'rxjs'; + +@Component({ + selector: 'app-feature-name', + standalone: true, + imports: [CommonModule], + templateUrl: './feature-name.component.html', + styleUrls: ['./feature-name.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class FeatureNameComponent implements OnInit, OnDestroy { + private destroy$ = new Subject(); + + ngOnInit(): void { + // Initialization logic + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } +} +``` + +### Always: +- Use OnPush change detection strategy +- Implement OnDestroy for cleanup +- Use standalone components for new code +- Type everything properly, avoid `any` +- Use async pipe in templates + +### Never: +- Mutate state directly +- Forget to unsubscribe from observables +- Use nested subscriptions +- Put business logic in components + +## Service Patterns + +```typescript +import { Injectable } from '@angular/core'; +import { Observable, catchError, map, shareReplay } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; + +@Injectable({ providedIn: 'root' }) +export class DataService { + private cache$ = new Map>(); + + constructor(private http: HttpClient) {} + + getData(id: string): Observable { + if (!this.cache$.has(id)) { + this.cache$.set( + id, + this.http.get(`/api/data/${id}`).pipe( + shareReplay(1), + catchError(this.handleError) + ) + ); + } + return this.cache$.get(id)!; + } + + private handleError(error: any): Observable { + console.error('An error occurred:', error); + throw error; + } +} +``` + +## RxJS Best Practices + +### Use Proper Operators +- `switchMap`: Cancel previous request (search, navigation) +- `mergeMap`: Parallel requests (batch operations) +- `concatMap`: Sequential requests (ordered operations) +- `exhaustMap`: Ignore new requests until current completes (form submit) + +### Memory Management +```typescript +// Good: Using takeUntil +this.dataService.getData() + .pipe(takeUntil(this.destroy$)) + .subscribe(data => this.data = data); + +// Better: Using async pipe +data$ = this.dataService.getData(); +// Template: {{ data$ | async }} +``` + +## ABP Framework Integration + +### Service Injection +```typescript +import { + ConfigStateService, + LocalizationService, + PermissionService +} from '@abp/ng.core'; + +constructor( + private config: ConfigStateService, + private localization: LocalizationService, + private permission: PermissionService +) {} +``` + +### Localization Usage +```typescript +// Component +readonly localizationKeys = { + title: this.localization.instant('::PageTitle'), + save: this.localization.instant('::Save'), + cancel: this.localization.instant('::Cancel') +}; + +// Template +

{{ '::PageTitle' | abpLocalization }}

+``` + +### Permission Checks +```typescript +// Template + + +// Component +canCreate$ = this.config.getGrantedPolicy$('MyApp.Users.Create'); +``` + +### Using ABP Proxy Services +```typescript +import { UserService } from '@proxy/users'; + +constructor(private userService: UserService) {} + +ngOnInit(): void { + this.users$ = this.userService.getList({ maxResultCount: 10 }); +} +``` + +## State Management (NGXS) + +```typescript +import { State, Action, StateContext, Selector } from '@ngxs/store'; +import { tap } from 'rxjs'; + +export class GetUsers { + static readonly type = '[Users] Get Users'; +} + +export interface UsersStateModel { + users: User[]; + loading: boolean; +} + +@State({ + name: 'users', + defaults: { + users: [], + loading: false + } +}) +@Injectable() +export class UsersState { + constructor(private userService: UserService) {} + + @Selector() + static getUsers(state: UsersStateModel) { + return state.users; + } + + @Action(GetUsers) + getUsers(ctx: StateContext) { + ctx.patchState({ loading: true }); + return this.userService.getList().pipe( + tap(response => { + ctx.patchState({ + users: response.items, + loading: false + }); + }) + ); + } +} +``` + +## Form Handling + +```typescript +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; + +export class FormComponent implements OnInit { + form!: FormGroup; + + constructor(private fb: FormBuilder) {} + + ngOnInit(): void { + this.form = this.fb.group({ + name: ['', [Validators.required, Validators.minLength(3)]], + email: ['', [Validators.required, Validators.email]], + age: [null, [Validators.min(18), Validators.max(100)]] + }); + } + + onSubmit(): void { + if (this.form.valid) { + const formValue = this.form.getRawValue(); + // Submit logic + } + } +} +``` + +## Template Best Practices + +```html + +
+ {{ data.name }} +
+ + +
+ {{ item.name }} +
+ + + +``` + +## Common Patterns to Avoid +❌ Manual subscriptions without cleanup +❌ Logic in templates +❌ Mutating input properties +❌ Using `any` type +❌ Nested subscriptions +❌ Missing error handling + +## Common Patterns to Use +✅ Async pipe in templates +✅ OnPush change detection +✅ Reactive forms +✅ Smart/dumb components +✅ Dependency injection +✅ RxJS operators +✅ Proper typing + +Follow these rules consistently to maintain high code quality. diff --git a/npm/ng-packs/packages/schematics/src/commands/ai-config/files/gemini/.gemini/GEMINI.md b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/gemini/.gemini/GEMINI.md new file mode 100644 index 0000000000..83e598bfb0 --- /dev/null +++ b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/gemini/.gemini/GEMINI.md @@ -0,0 +1,240 @@ +# Gemini AI - Angular & ABP Framework Development Guidelines + +## Project Overview +This is an enterprise Angular application using the ABP Framework with Nx workspace structure and NGXS for state management. + +## Angular Development Standards + +### Component Architecture +Always create components with: +- OnPush change detection strategy +- Proper lifecycle hook implementation (OnDestroy for cleanup) +- Standalone components for new features +- TypeScript strict typing (avoid `any`) + +```typescript +@Component({ + selector: 'app-example', + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [CommonModule, ReactiveFormsModule] +}) +export class ExampleComponent implements OnInit, OnDestroy { + private destroy$ = new Subject(); + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } +} +``` + +### Service Development +- Use `providedIn: 'root'` for singleton services +- Return Observables for async operations +- Implement proper error handling with RxJS operators +- Use dependency injection properly + +```typescript +@Injectable({ providedIn: 'root' }) +export class DataService { + constructor(private http: HttpClient) {} + + getData(): Observable { + return this.http.get('/api/data').pipe( + retry(2), + catchError(this.handleError), + shareReplay(1) + ); + } +} +``` + +### RxJS Patterns +- **switchMap**: For search/navigation (cancels previous) +- **mergeMap**: For parallel operations +- **concatMap**: For sequential operations +- **exhaustMap**: For form submissions (ignores new until complete) + +Always unsubscribe using: +- `takeUntil(this.destroy$)` pattern +- `async` pipe in templates (preferred) +- `take(1)` for single emissions + +### Template Best Practices +```html + +
+
+ {{ item.name }} +
+
+ + +``` + +## ABP Framework Integration + +### Localization +```typescript +// Component +this.localization.instant('::LocalizationKey') + +// Template +{{ '::WelcomeMessage' | abpLocalization }} +``` + +### Permissions +```typescript +// Template + + +// Component +canEdit$ = this.config.getGrantedPolicy$('MyApp.Books.Edit'); +``` + +### API Proxy Services +Always use ABP's generated proxy services instead of manual HTTP calls: + +```typescript +import { BookService } from '@proxy/books'; + +constructor(private bookService: BookService) {} + +getBooks(): Observable> { + return this.bookService.getList({ maxResultCount: 10 }); +} +``` + +### State Management (NGXS) +```typescript +@State({ + name: 'books', + defaults: { books: [], loading: false } +}) +@Injectable() +export class BooksState { + @Selector() + static getBooks(state: BooksStateModel) { + return state.books; + } + + @Action(GetBooks) + getBooks(ctx: StateContext) { + ctx.patchState({ loading: true }); + return this.bookService.getList().pipe( + tap(response => { + ctx.patchState({ + books: response.items, + loading: false + }); + }) + ); + } +} +``` + +## Code Quality Standards + +### TypeScript +- Use strict mode +- Avoid `any` type +- Use interfaces and types properly +- Implement proper null checks + +### Testing +- Write unit tests with Jest +- Mock dependencies properly +- Test both success and error scenarios +- Aim for good coverage + +### Performance +- Use OnPush change detection +- Lazy load feature modules +- Implement trackBy for lists +- Use production builds +- Avoid memory leaks + +### Security +- Sanitize user inputs +- Use Angular's built-in XSS protection +- Validate on client and server +- Follow ABP's security patterns +- Never expose sensitive data + +## File Structure (Nx Workspace) +``` +libs/ + feature-name/ + src/ + lib/ + components/ + services/ + models/ + state/ + guards/ +``` + +## Common Patterns + +### Smart/Dumb Components +```typescript +// Smart (Container) +@Component({ + template: ` + + + ` +}) +export class ContainerComponent { + items$ = this.store.select(getItems); + constructor(private store: Store) {} +} + +// Dumb (Presentational) +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ListComponent { + @Input() items: Item[] = []; + @Output() itemSelected = new EventEmitter(); +} +``` + +### Reactive Forms +```typescript +form = this.fb.group({ + name: ['', [Validators.required, Validators.minLength(3)]], + email: ['', [Validators.required, Validators.email]] +}); +``` + +## Best Practices Checklist +✅ OnPush change detection +✅ Proper unsubscription +✅ Async pipe in templates +✅ TypeScript strict typing +✅ Error handling +✅ Unit tests +✅ Localization (no hardcoded strings) +✅ Permission checks +✅ Accessibility attributes +✅ Performance optimization + +## Anti-Patterns to Avoid +❌ Using `any` type +❌ Manual subscriptions without cleanup +❌ Logic in templates +❌ Nested subscriptions +❌ Mutating state directly +❌ Missing error handling +❌ Hardcoded strings + +## Resources +- Angular Style Guide: https://angular.io/guide/styleguide +- ABP Documentation: https://docs.abp.io +- RxJS Operators: https://rxjs.dev/guide/operators + +Always prioritize code maintainability, readability, and following Angular and ABP Framework best practices. diff --git a/npm/ng-packs/packages/schematics/src/commands/ai-config/files/junie/.junie/guidelines.md b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/junie/.junie/guidelines.md new file mode 100644 index 0000000000..f7656afa38 --- /dev/null +++ b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/junie/.junie/guidelines.md @@ -0,0 +1,352 @@ +# Junie AI Guidelines - Angular & ABP Framework + +## Introduction +You are assisting with an Angular application built on the ABP Framework. Follow these guidelines to generate high-quality, maintainable code that adheres to best practices. + +## Core Principles +1. **Type Safety**: Use TypeScript strict mode, avoid `any` +2. **Performance**: OnPush change detection, lazy loading +3. **Maintainability**: Clean, readable, well-documented code +4. **Security**: Input validation, proper authentication/authorization +5. **Accessibility**: WCAG 2.1 compliance, ARIA attributes + +## Angular Component Guidelines + +### Component Structure +```typescript +import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Subject, takeUntil } from 'rxjs'; + +@Component({ + selector: 'app-feature', + standalone: true, + imports: [CommonModule], + templateUrl: './feature.component.html', + styleUrls: ['./feature.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class FeatureComponent implements OnInit, OnDestroy { + private readonly destroy$ = new Subject(); + + ngOnInit(): void { + // Initialization + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } +} +``` + +### Key Requirements +- ✅ Use OnPush change detection +- ✅ Implement OnDestroy for cleanup +- ✅ Prefer standalone components +- ✅ Use proper TypeScript types +- ✅ Follow smart/dumb component pattern + +## Service Development + +```typescript +@Injectable({ providedIn: 'root' }) +export class DataService { + constructor(private http: HttpClient) {} + + getData(): Observable { + return this.http.get('/api/data').pipe( + retry(2), + catchError(this.handleError), + shareReplay(1) + ); + } + + private handleError(error: HttpErrorResponse): Observable { + console.error('Service error:', error); + return throwError(() => new Error('Operation failed')); + } +} +``` + +## RxJS Best Practices + +### Operator Selection +| Operator | Use Case | Example | +|----------|----------|---------| +| `switchMap` | Search, navigation (cancel previous) | Search input | +| `mergeMap` | Parallel operations | Batch API calls | +| `concatMap` | Sequential operations | Ordered processing | +| `exhaustMap` | Ignore until complete | Form submission | + +### Subscription Management +```typescript +// ✅ BEST: Use async pipe +data$ = this.service.getData(); + +// ✅ GOOD: Use takeUntil +this.service.getData() + .pipe(takeUntil(this.destroy$)) + .subscribe(data => this.handleData(data)); + +// ❌ BAD: No unsubscription +this.service.getData().subscribe(data => this.data = data); +``` + +## ABP Framework Integration + +### Localization +```typescript +// Service injection +constructor(private localization: LocalizationService) {} + +// Usage in component +getTranslation(key: string): string { + return this.localization.instant(`::${key}`); +} + +// Template usage +{{ '::PageTitle' | abpLocalization }} +``` + +### Permission System +```typescript +// Directive in template + + +// Check in component +canEdit(): boolean { + return this.config.getGrantedPolicy('MyApp.Books.Edit'); +} + +// Observable permission +canEdit$ = this.config.getGrantedPolicy$('MyApp.Books.Edit'); +``` + +### API Proxy Services +```typescript +// ✅ DO: Use generated proxy +import { BookService } from '@proxy/books'; + +constructor(private bookService: BookService) {} + +loadBooks(): void { + this.books$ = this.bookService.getList({ maxResultCount: 10 }); +} + +// ❌ DON'T: Manual HTTP calls for ABP APIs +``` + +### State Management (NGXS) +```typescript +// Actions +export class LoadBooks { + static readonly type = '[Books] Load Books'; +} + +// State +@State({ + name: 'books', + defaults: { books: [], loading: false } +}) +@Injectable() +export class BooksState { + constructor(private bookService: BookService) {} + + @Selector() + static books(state: BooksStateModel) { + return state.books; + } + + @Action(LoadBooks) + loadBooks(ctx: StateContext) { + ctx.patchState({ loading: true }); + return this.bookService.getList().pipe( + tap(response => ctx.patchState({ + books: response.items, + loading: false + })) + ); + } +} +``` + +## Forms + +### Reactive Forms +```typescript +export class FormComponent implements OnInit { + form: FormGroup; + + constructor(private fb: FormBuilder) {} + + ngOnInit(): void { + this.form = this.fb.group({ + name: ['', [Validators.required, Validators.maxLength(100)]], + email: ['', [Validators.required, Validators.email]], + age: [null, [Validators.min(0), Validators.max(120)]] + }); + } + + onSubmit(): void { + if (this.form.valid) { + const formData = this.form.getRawValue(); + this.submitData(formData); + } + } +} +``` + +## Template Best Practices + +```html + +
+
+

{{ user.name }}

+

{{ user.email }}

+
+
+ + + + + +

{{ '::WelcomeMessage' | abpLocalization }}

+``` + +## Testing + +```typescript +describe('BookService', () => { + let service: BookService; + let httpMock: HttpTestingController; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [BookService] + }); + service = TestBed.inject(BookService); + httpMock = TestBed.inject(HttpTestingController); + }); + + it('should fetch books', () => { + const mockBooks = [{ id: 1, title: 'Test' }]; + + service.getList().subscribe(books => { + expect(books).toEqual(mockBooks); + }); + + const req = httpMock.expectOne('/api/app/books'); + req.flush(mockBooks); + }); +}); +``` + +## Performance Optimization + +### Change Detection +- Use OnPush strategy everywhere possible +- Avoid function calls in templates +- Use pure pipes +- Implement trackBy for lists + +### Lazy Loading +```typescript +const routes: Routes = [ + { + path: 'books', + loadChildren: () => import('./books/books.module') + .then(m => m.BooksModule) + } +]; +``` + +### Bundle Optimization +- Use standalone components +- Implement lazy loading +- Use dynamic imports +- Tree-shake unused code + +## Security Best Practices + +1. **Input Validation**: Always validate user input +2. **Sanitization**: Use DomSanitizer when needed +3. **XSS Prevention**: Leverage Angular's built-in protection +4. **Authentication**: Use ABP's auth system +5. **Authorization**: Check permissions properly +6. **Data Protection**: Never expose sensitive data in client + +## Code Quality Checklist + +Before submitting code, ensure: +- [ ] TypeScript strict mode enabled +- [ ] No `any` types used +- [ ] OnPush change detection applied +- [ ] Proper unsubscription implemented +- [ ] Error handling in place +- [ ] Unit tests written +- [ ] Localization keys used (no hardcoded text) +- [ ] Permission checks added +- [ ] Accessibility attributes included +- [ ] Performance optimized + +## File Organization (Nx Workspace) + +``` +libs/ + feature-name/ + src/ + lib/ + components/ + component-name/ + component-name.component.ts + component-name.component.html + component-name.component.scss + component-name.component.spec.ts + services/ + models/ + state/ + guards/ + pipes/ + directives/ + index.ts (public API) +``` + +## Common Patterns + +### Smart/Dumb Components +- **Smart**: Container with business logic, state management +- **Dumb**: Presentational with @Input/@Output, OnPush + +### Service Layer +- **API Services**: Backend communication +- **Business Services**: Business logic +- **Utility Services**: Helper functions + +## Anti-Patterns to Avoid + +❌ Using `any` type +❌ Forgetting to unsubscribe +❌ Complex logic in templates +❌ Nested subscriptions +❌ Direct state mutation +❌ Missing error handling +❌ Hardcoded strings +❌ Skipping unit tests + +## Additional Resources + +- Angular Style Guide: https://angular.io/guide/styleguide +- ABP Framework Docs: https://docs.abp.io +- RxJS Documentation: https://rxjs.dev +- Nx Documentation: https://nx.dev +- NGXS Documentation: https://www.ngxs.io + +Follow these guidelines consistently to produce high-quality, maintainable Angular applications with ABP Framework. diff --git a/npm/ng-packs/packages/schematics/src/commands/ai-config/files/windsurf/.windsurf/rules/guidelines.md b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/windsurf/.windsurf/rules/guidelines.md new file mode 100644 index 0000000000..7449f4f582 --- /dev/null +++ b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/windsurf/.windsurf/rules/guidelines.md @@ -0,0 +1,652 @@ +# Windsurf AI Development Guidelines - Angular & ABP Framework + +## Project Context +This is an enterprise-grade Angular application built on the ABP Framework, using Nx for workspace management and NGXS for state management. Follow these comprehensive guidelines to generate production-ready code. + +--- + +## 🎯 Core Development Principles + +### 1. Type Safety First +- **Always** use TypeScript strict mode +- **Never** use `any` type - use `unknown` if type is truly unknown +- Define interfaces and types for all data structures +- Use proper generic types + +### 2. Performance Optimization +- Use OnPush change detection strategy by default +- Implement lazy loading for feature modules +- Use trackBy with *ngFor directives +- Leverage async pipe for observables +- Avoid memory leaks with proper cleanup + +### 3. Code Maintainability +- Follow SOLID principles +- Write self-documenting code with clear naming +- Keep functions small (<20 lines ideally) +- Add JSDoc comments for complex logic +- Use meaningful variable and function names + +### 4. Security +- Validate all user inputs +- Sanitize data when necessary (DomSanitizer) +- Use ABP's permission system +- Never expose sensitive data in client code +- Follow OWASP security guidelines + +### 5. Accessibility +- Include ARIA attributes +- Support keyboard navigation +- Use semantic HTML +- Follow WCAG 2.1 AA standards + +--- + +## 📦 Angular Component Architecture + +### Standard Component Structure + +```typescript +import { + ChangeDetectionStrategy, + Component, + OnDestroy, + OnInit, + inject +} from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Subject, takeUntil } from 'rxjs'; + +@Component({ + selector: 'app-feature-name', + standalone: true, + imports: [CommonModule], + templateUrl: './feature-name.component.html', + styleUrls: ['./feature-name.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class FeatureNameComponent implements OnInit, OnDestroy { + // Use inject() function (Angular 14+) + private readonly dataService = inject(DataService); + private readonly destroy$ = new Subject(); + + // Observable streams with $ suffix + data$ = this.dataService.getData(); + + ngOnInit(): void { + // Initialization logic + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } +} +``` + +### Component Best Practices +✅ **DO:** +- Use OnPush change detection +- Implement OnDestroy for cleanup +- Use standalone components for new code +- Prefer async pipe over manual subscriptions +- Use readonly for immutable properties +- Use inject() function for dependency injection + +❌ **DON'T:** +- Put business logic in components +- Mutate @Input() properties +- Forget to unsubscribe from observables +- Use function calls in templates +- Use nested subscriptions + +--- + +## 🔧 Service Development + +### Service Pattern + +```typescript +import { Injectable, inject } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable, catchError, retry, shareReplay, throwError } from 'rxjs'; + +@Injectable({ providedIn: 'root' }) +export class DataService { + private readonly http = inject(HttpClient); + private readonly cache$ = new Map>(); + + getData(id: string): Observable { + // Implement caching + if (!this.cache$.has(id)) { + this.cache$.set( + id, + this.http.get(`/api/data/${id}`).pipe( + retry(2), + catchError(this.handleError), + shareReplay(1) + ) + ); + } + return this.cache$.get(id)!; + } + + private handleError(error: HttpErrorResponse): Observable { + console.error('Service error:', error); + // Log to monitoring service here + return throwError(() => new Error('Operation failed. Please try again.')); + } +} +``` + +### Service Best Practices +- Use `providedIn: 'root'` for singleton services +- Return Observables for async operations +- Implement proper error handling +- Use caching strategies when appropriate +- Keep services focused (Single Responsibility) + +--- + +## 🌊 RxJS Patterns & Operators + +### Operator Decision Matrix + +| Operator | Use Case | Behavior | +|----------|----------|----------| +| **switchMap** | Search, navigation | Cancels previous, emits latest | +| **mergeMap** | Parallel operations | Runs all concurrently | +| **concatMap** | Sequential operations | Maintains order, waits for completion | +| **exhaustMap** | Form submission, clicks | Ignores new until current completes | + +### Subscription Management + +```typescript +export class ExampleComponent implements OnDestroy { + private readonly destroy$ = new Subject(); + + ngOnInit(): void { + // Pattern 1: takeUntil + this.service.getData() + .pipe(takeUntil(this.destroy$)) + .subscribe(data => this.handleData(data)); + + // Pattern 2: take(1) for single emission + this.service.getConfig() + .pipe(take(1)) + .subscribe(config => this.config = config); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } +} +``` + +### Template Usage (Preferred) + +```typescript +// Component +data$ = this.service.getData().pipe( + catchError(error => { + this.handleError(error); + return of([]); + }) +); + +// Template +
+ {{ data.name }} +
+``` + +--- + +## 🏗️ ABP Framework Integration + +### 1. Localization System + +```typescript +// Component +import { LocalizationService } from '@abp/ng.core'; + +export class MyComponent { + private readonly localization = inject(LocalizationService); + + readonly texts = { + title: this.localization.instant('::PageTitle'), + save: this.localization.instant('::Save'), + cancel: this.localization.instant('::Cancel') + }; +} + +// Template +

{{ '::PageTitle' | abpLocalization }}

+ +``` + +### 2. Permission System + +```typescript +// Template + + +// Component +import { ConfigStateService } from '@abp/ng.core'; + +export class BookListComponent { + private readonly config = inject(ConfigStateService); + + canEdit$ = this.config.getGrantedPolicy$('BookStore.Books.Edit'); + canDelete$ = this.config.getGrantedPolicy$('BookStore.Books.Delete'); + + checkPermission(): boolean { + return this.config.getGrantedPolicy('BookStore.Books.Create'); + } +} +``` + +### 3. API Proxy Services + +```typescript +// ✅ ALWAYS use generated proxy services +import { BookService } from '@proxy/books'; +import { GetBooksInput } from '@proxy/books/models'; + +export class BookListComponent { + private readonly bookService = inject(BookService); + + books$ = this.bookService.getList({ + maxResultCount: 10, + skipCount: 0 + }); + + createBook(input: CreateBookDto): void { + this.bookService.create(input).pipe( + take(1), + catchError(this.handleError) + ).subscribe(() => this.refreshList()); + } +} + +// ❌ DON'T create manual HTTP calls for ABP APIs +``` + +### 4. State Management with NGXS + +```typescript +// Actions +export class GetBooks { + static readonly type = '[Books] Get Books'; + constructor(public payload: GetBooksInput) {} +} + +export class CreateBook { + static readonly type = '[Books] Create Book'; + constructor(public payload: CreateBookDto) {} +} + +// State +export interface BooksStateModel { + books: BookDto[]; + loading: boolean; + error: string | null; + totalCount: number; +} + +@State({ + name: 'books', + defaults: { + books: [], + loading: false, + error: null, + totalCount: 0 + } +}) +@Injectable() +export class BooksState { + private readonly bookService = inject(BookService); + + @Selector() + static books(state: BooksStateModel): BookDto[] { + return state.books; + } + + @Selector() + static loading(state: BooksStateModel): boolean { + return state.loading; + } + + @Selector() + static totalCount(state: BooksStateModel): number { + return state.totalCount; + } + + @Action(GetBooks) + getBooks(ctx: StateContext, action: GetBooks) { + ctx.patchState({ loading: true, error: null }); + + return this.bookService.getList(action.payload).pipe( + tap(response => { + ctx.patchState({ + books: response.items, + totalCount: response.totalCount, + loading: false + }); + }), + catchError(error => { + ctx.patchState({ + loading: false, + error: error.message + }); + return throwError(() => error); + }) + ); + } + + @Action(CreateBook) + createBook(ctx: StateContext, action: CreateBook) { + return this.bookService.create(action.payload).pipe( + tap(book => { + const state = ctx.getState(); + ctx.patchState({ + books: [...state.books, book], + totalCount: state.totalCount + 1 + }); + }) + ); + } +} +``` + +### 5. Multi-Tenancy Support + +```typescript +import { ConfigStateService } from '@abp/ng.core'; + +export class TenantAwareComponent { + private readonly config = inject(ConfigStateService); + + get currentTenant() { + return this.config.getOne('currentTenant'); + } + + get isTenantContext(): boolean { + return !!this.currentTenant?.id; + } +} +``` + +--- + +## 📝 Reactive Forms + +### Form Implementation + +```typescript +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { CustomValidators } from './validators'; + +export class BookFormComponent implements OnInit { + private readonly fb = inject(FormBuilder); + + bookForm!: FormGroup; + + ngOnInit(): void { + this.bookForm = this.fb.group({ + name: ['', [ + Validators.required, + Validators.minLength(3), + Validators.maxLength(128) + ]], + type: ['', Validators.required], + publishDate: ['', [ + Validators.required, + CustomValidators.notFutureDate + ]], + price: [0, [ + Validators.required, + Validators.min(0), + Validators.max(999999.99) + ]], + description: ['', Validators.maxLength(1000)] + }); + } + + onSubmit(): void { + if (this.bookForm.valid) { + const formValue = this.bookForm.getRawValue(); + this.submitForm(formValue); + } else { + this.markFormGroupTouched(this.bookForm); + } + } + + private markFormGroupTouched(formGroup: FormGroup): void { + Object.keys(formGroup.controls).forEach(key => { + const control = formGroup.get(key); + control?.markAsTouched(); + + if (control instanceof FormGroup) { + this.markFormGroupTouched(control); + } + }); + } +} +``` + +--- + +## 🎨 Template Best Practices + +```html + +
+
+

{{ book.name }}

+

{{ book.publishDate | date:'shortDate' }}

+

{{ book.price | currency }}

+
+
+ + +
Loading...
+
+ + + + + +
+ +
+ + +

{{ '::BookManagement' | abpLocalization }}

+

{{ '::BookDescription' | abpLocalization:{ name: book.name } }}

+``` + +--- + +## 🧪 Testing Strategies + +### Component Testing + +```typescript +describe('BookListComponent', () => { + let component: BookListComponent; + let fixture: ComponentFixture; + let mockBookService: jasmine.SpyObj; + + beforeEach(async () => { + mockBookService = jasmine.createSpyObj('BookService', [ + 'getList', + 'create', + 'delete' + ]); + + await TestBed.configureTestingModule({ + imports: [BookListComponent], + providers: [ + { provide: BookService, useValue: mockBookService } + ] + }).compileComponents(); + + fixture = TestBed.createComponent(BookListComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should load books on init', () => { + const mockBooks = [ + { id: '1', name: 'Book 1', price: 10 }, + { id: '2', name: 'Book 2', price: 20 } + ]; + + mockBookService.getList.and.returnValue(of({ + items: mockBooks, + totalCount: 2 + })); + + component.ngOnInit(); + + expect(mockBookService.getList).toHaveBeenCalled(); + }); +}); +``` + +--- + +## 🚀 Performance Optimization + +### 1. Change Detection Strategy +```typescript +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush +}) +``` + +### 2. TrackBy Functions +```typescript +trackByBookId(index: number, book: BookDto): string { + return book.id; +} +``` + +### 3. Lazy Loading +```typescript +const routes: Routes = [ + { + path: 'books', + loadChildren: () => import('./books/books.routes') + .then(m => m.BOOKS_ROUTES) + } +]; +``` + +### 4. Virtual Scrolling (for large lists) +```typescript + +
+ {{ book.name }} +
+
+``` + +--- + +## 📁 File Structure (Nx Workspace) + +``` +libs/ + books/ + feature/ + src/ + lib/ + components/ + book-list/ + book-form/ + book-detail/ + services/ + state/ + guards/ + books-feature.routes.ts + index.ts + data-access/ + src/ + lib/ + services/ + models/ + index.ts + ui/ + src/ + lib/ + components/ + index.ts +``` + +--- + +## ✅ Quality Checklist + +Before committing code, verify: +- [ ] TypeScript strict mode compliance +- [ ] No `any` types +- [ ] OnPush change detection +- [ ] Proper unsubscription +- [ ] Error handling implemented +- [ ] Unit tests written +- [ ] Localization keys used +- [ ] Permission checks added +- [ ] Accessibility attributes included +- [ ] Performance optimized (trackBy, lazy loading) +- [ ] Security validated +- [ ] Code formatted (Prettier) +- [ ] Linting passed + +--- + +## 🚫 Common Anti-Patterns + +| Anti-Pattern | Why It's Bad | Better Approach | +|--------------|--------------|-----------------| +| Using `any` | Loses type safety | Use proper types or `unknown` | +| No unsubscribe | Memory leaks | Use takeUntil or async pipe | +| Logic in templates | Hard to test | Move to component/service | +| Nested subscriptions | Hard to maintain | Use RxJS operators | +| Direct state mutation | Breaks change detection | Use immutable patterns | +| Missing error handling | Poor UX | Always handle errors | +| Hardcoded strings | Not localizable | Use localization system | + +--- + +## 📚 Resources + +- [Angular Style Guide](https://angular.io/guide/styleguide) +- [ABP Framework Documentation](https://docs.abp.io) +- [RxJS Documentation](https://rxjs.dev) +- [Nx Documentation](https://nx.dev) +- [NGXS Documentation](https://www.ngxs.io) + +--- + +Follow these guidelines to build maintainable, performant, and secure Angular applications with ABP Framework. diff --git a/npm/ng-packs/packages/schematics/src/commands/ai-config/index.ts b/npm/ng-packs/packages/schematics/src/commands/ai-config/index.ts new file mode 100644 index 0000000000..10691f6609 --- /dev/null +++ b/npm/ng-packs/packages/schematics/src/commands/ai-config/index.ts @@ -0,0 +1,104 @@ +import { Rule, SchematicsException, Tree, apply, url, mergeWith, MergeStrategy, filter, chain } from '@angular-devkit/schematics'; +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) { + 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) { + throw new SchematicsException( + `Project "${options.targetProject}" not found in workspace.` + ); + } + targetPath = normalize(project.root); + } + + console.log('🚀 Generating AI configuration files...'); + console.log(`📁 Target path: ${targetPath}`); + console.log(`🤖 Selected tools: ${options.tool.join(', ')}`); + + // Generate rules for each selected tool + const rules: Rule[] = options.tool + .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 => { + const configPath = getConfigPath(tool, targetPath); + console.log(` - ${configPath}`); + }); + + console.log('\n💡 Tip: Restart your IDE or AI tool to apply the new configurations.'); + + return tree; + } + ]); + }; +} + +/** + * 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 = { + claude: '.claude/CLAUDE.md', + copilot: '.github/copilot-instructions.md', + cursor: '.cursor/rules/cursor.mdc', + gemini: '.gemini/GEMINI.md', + junie: '.junie/guidelines.md', + windsurf: '.windsurf/rules/guidelines.md' + }; + + const configFile = configFiles[tool]; + return join(normalize(basePath), configFile); +} diff --git a/npm/ng-packs/packages/schematics/src/commands/ai-config/model.ts b/npm/ng-packs/packages/schematics/src/commands/ai-config/model.ts new file mode 100644 index 0000000000..8a10f85509 --- /dev/null +++ b/npm/ng-packs/packages/schematics/src/commands/ai-config/model.ts @@ -0,0 +1,12 @@ +export type AiTool = 'claude' | 'copilot' | 'cursor' | 'gemini' | 'junie' | 'windsurf'; + +export interface AiConfigSchema { + tool: AiTool[]; + targetProject?: string; + overwrite?: boolean; +} + +export interface AiConfigFile { + path: string; + content: string; +} diff --git a/npm/ng-packs/packages/schematics/src/commands/ai-config/schema.json b/npm/ng-packs/packages/schematics/src/commands/ai-config/schema.json new file mode 100644 index 0000000000..7ad18a1fe4 --- /dev/null +++ b/npm/ng-packs/packages/schematics/src/commands/ai-config/schema.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/schema", + "$id": "SchematicsABPAiConfig", + "title": "ABP AI Configuration Generator Schema", + "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": [], + "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" } + ] + } + }, + "targetProject": { + "description": "The target project name to generate AI configuration files for", + "type": "string", + "x-prompt": { + "message": "Which project would you like to generate AI config for?", + "type": "input" + } + }, + "overwrite": { + "description": "Overwrite existing AI configuration files", + "type": "boolean", + "default": false + } + }, + "required": [] +} diff --git a/npm/ng-packs/scripts/build-schematics.ts b/npm/ng-packs/scripts/build-schematics.ts index 0699eb0567..da9daf8769 100644 --- a/npm/ng-packs/scripts/build-schematics.ts +++ b/npm/ng-packs/scripts/build-schematics.ts @@ -22,6 +22,8 @@ const PACKAGE_TO_BUILD = 'schematics'; const FILES_TO_COPY_AFTER_BUILD: (FileCopy | string)[] = [ { src: 'src/commands/create-lib/schema.json', dest: 'commands/create-lib/schema.json' }, { src: 'src/commands/change-theme/schema.json', dest: 'commands/change-theme/schema.json' }, + { src: 'src/commands/ai-config/schema.json', dest: 'commands/ai-config/schema.json' }, + { src: 'src/commands/ai-config/files', dest: 'commands/ai-config/files' }, { src: 'src/commands/create-lib/files-package', dest: 'commands/create-lib/files-package' }, { src: 'src/commands/create-lib/files-package-standalone', dest: 'commands/create-lib/files-package-standalone' }, { From c7a4557235f85bb2d07f26b8ea9c63fe9dbdd782 Mon Sep 17 00:00:00 2001 From: Fahri Gedik Date: Wed, 8 Oct 2025 09:01:36 +0300 Subject: [PATCH 02/27] Unify ABP + Angular full-stack guidelines across AIs Replaces individual AI-specific Angular and ABP Framework rules with a single, comprehensive set of full-stack development guidelines. The new rules emphasize modular architecture, strict typing, best practices for both .NET (ABP) and Angular, and ensure consistency, maintainability, and performance across backend and frontend. This change standardizes expectations and coding conventions for all AI assistants in the project. --- .../ai-config/files/claude/.claude/CLAUDE.md | 258 +++--- .../copilot/.github/copilot-instructions.md | 306 ++++--- .../files/cursor/.cursor/rules/cursor.mdc | 427 ++++------ .../ai-config/files/gemini/.gemini/GEMINI.md | 388 ++++----- .../files/junie/.junie/guidelines.md | 508 ++++-------- .../windsurf/.windsurf/rules/guidelines.md | 776 ++++-------------- 6 files changed, 910 insertions(+), 1753 deletions(-) diff --git a/npm/ng-packs/packages/schematics/src/commands/ai-config/files/claude/.claude/CLAUDE.md b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/claude/.claude/CLAUDE.md index 66c1a515ee..a150753322 100644 --- a/npm/ng-packs/packages/schematics/src/commands/ai-config/files/claude/.claude/CLAUDE.md +++ b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/claude/.claude/CLAUDE.md @@ -1,106 +1,156 @@ -# Angular & ABP Framework Development Rules for Claude - -## Project Context -This is an Angular application built with the ABP Framework. Follow these rules to generate high-quality, maintainable code. - -## Angular Best Practices - -### Component Development -- Use OnPush change detection strategy by default -- Implement OnDestroy and unsubscribe from observables -- Keep components focused on presentation logic -- Use smart/dumb component pattern -- Prefer standalone components in new code -- Use proper TypeScript typing, avoid `any` - -### Service Development -- Make services injectable with `providedIn: 'root'` when possible -- Use dependency injection properly -- Handle errors appropriately with RxJS operators -- Return observables for async operations -- Keep services focused on single responsibility - -### RxJS Best Practices -- Use proper operators: `switchMap`, `mergeMap`, `concatMap`, `exhaustMap` -- Always unsubscribe using `takeUntil`, `take`, or async pipe -- Avoid nested subscriptions -- Use `shareReplay` for shared streams -- Handle errors with `catchError` - -### Template Best Practices -- Use async pipe for observables -- Avoid complex logic in templates -- Use trackBy with *ngFor -- Use proper change detection -- Follow accessibility guidelines (ARIA attributes) - -## ABP Framework Specific Rules - -### Module Structure -- Follow ABP's modular architecture -- Use feature modules appropriately -- Leverage ABP's configuration services -- Use ABP's localization system +# 💻 ABP Full-Stack Development Rules +_Expert Guidelines for .NET Backend (ABP) and Angular Frontend Development_ + +You are a **senior full-stack developer** specializing in **ABP Framework (.NET)** and **Angular (TypeScript)**. +You write **clean, maintainable, and modular** code following **ABP, ASP.NET Core, and Angular best practices**. + +--- + +## 🧩 1. General Principles +- Maintain a clear separation between backend (ABP/.NET) and frontend (Angular) layers. +- Follow **modular architecture** — each layer or feature should be independently testable and reusable. +- Always adhere to **official ABP documentation** ([docs.abp.io](https://docs.abp.io)) and **Angular official guides**. +- Prioritize **readability, maintainability, and performance**. +- Write **idiomatic** and **self-documenting** code. + +--- + +## ⚙️ 2. ABP / .NET Development Rules + +### Code Style and Structure +- Follow ABP’s standard folder structure: + - `*.Application`, `*.Domain`, `*.EntityFrameworkCore`, `*.HttpApi` +- Write concise, idiomatic C# code using modern language features. +- Apply **modular and layered design** (Domain, Application, Infrastructure, UI). +- Prefer **LINQ** and **lambda expressions** for collection operations. +- Use **descriptive method and variable names** (`GetActiveUsers`, `CalculateTotalAmount`). + +### Naming Conventions +- **PascalCase** → Classes, Methods, Properties +- **camelCase** → Local variables and private fields +- **UPPER_CASE** → Constants +- Prefix interfaces with **`I`** (e.g., `IUserRepository`). + +### C# and .NET Usage +- Use **C# 10+ features** (records, pattern matching, null-coalescing assignment). +- Utilize **ABP modules** (Permission Management, Setting Management, Audit Logging). +- Integrate **Entity Framework Core** with ABP’s repository abstractions. + +### Syntax and Formatting +- Follow [Microsoft C# Coding Conventions](https://learn.microsoft.com/dotnet/csharp/fundamentals/coding-style/coding-conventions). +- Use `var` when the type is clear. +- Use `string interpolation` and null-conditional operators. +- Keep code consistent and well-formatted. + +### Error Handling and Validation +- Use exceptions only for exceptional cases. +- Log errors via ABP’s built-in logging or a compatible provider. +- Validate models with **DataAnnotations** or **FluentValidation**. +- Rely on ABP’s global exception middleware for unified responses. +- Return consistent HTTP status codes and error DTOs. + +### API Design +- Build RESTful APIs via `HttpApi` layer and **ABP conventional controllers**. +- Use **attribute-based routing** and versioning when needed. +- Apply **action filters/middleware** for cross-cutting concerns (auditing, authorization). + +### Performance Optimization +- Use `async/await` for I/O operations. +- Use `IDistributedCache` over `IMemoryCache`. +- Avoid N+1 queries — include relations explicitly. +- Implement pagination with `PagedResultDto`. + +### Key Conventions +- Use **Dependency Injection** via ABP’s DI system. +- Apply **repository pattern** or EF Core directly as needed. +- Use **AutoMapper** or ABP object mapping for DTOs. +- Implement **background jobs** with ABP’s job system or `IHostedService`. +- Follow **domain-driven design (DDD)** principles: + - Business rules in Domain layer. + - Use `AuditedAggregateRoot`, `FullAuditedEntity`, etc. +- Avoid unnecessary dependencies between layers. + +### Testing +- Use **xUnit**, **Shouldly**, and **NSubstitute** for testing. +- Write **unit and integration tests** per module (`Application.Tests`, `Domain.Tests`). +- Mock dependencies properly and use ABP’s test base classes. + +### Security +- Use **OpenIddict** for authentication & authorization. +- Implement permission checks through ABP’s infrastructure. +- Enforce **HTTPS** and properly configure **CORS**. + +### API Documentation +- Use **Swagger / OpenAPI** (Swashbuckle or NSwag). +- Add XML comments to controllers and DTOs. +- Follow ABP’s documentation conventions for module APIs. + +**Reference Best Practices:** +- [Domain Services](https://abp.io/docs/latest/framework/architecture/best-practices/domain-services) +- [Repositories](https://abp.io/docs/latest/framework/architecture/best-practices/repositories) +- [Entities](https://abp.io/docs/latest/framework/architecture/best-practices/entities) +- [Application Services](https://abp.io/docs/latest/framework/architecture/best-practices/application-services) +- [DTOs](https://abp.io/docs/latest/framework/architecture/best-practices/data-transfer-objects) +- [Entity Framework Integration](https://abp.io/docs/latest/framework/architecture/best-practices/entity-framework-core-integration) + +--- + +## 🌐 3. Angular / TypeScript Development Rules + +### TypeScript Best Practices +- Enable **strict type checking** in `tsconfig.json`. +- Use **type inference** when the type is obvious. +- Avoid `any`; use `unknown` or generics instead. +- Use interfaces and types for clarity and structure. + +### Angular Best Practices +- Prefer **standalone components** (no `NgModules`). +- Do **NOT** set `standalone: true` manually — it’s default. +- Use **signals** for state management. +- Implement **lazy loading** for feature routes. +- Avoid `@HostBinding` / `@HostListener`; use `host` object in decorators. +- Use **`NgOptimizedImage`** for static images (not base64). + +### Components +- Keep components small, focused, and reusable. +- Use `input()` and `output()` functions instead of decorators. +- Use `computed()` for derived state. +- Always set `changeDetection: ChangeDetectionStrategy.OnPush`. +- Use **inline templates** for small components. +- Prefer **Reactive Forms** over template-driven forms. +- Avoid `ngClass` → use `[class]` bindings. +- Avoid `ngStyle` → use `[style]` bindings. ### State Management -- Use ABP's state management patterns -- Leverage NGXS for complex state -- Use ABP's store decorators properly - -### API Integration -- Use ABP's generated proxy services -- Follow ABP's REST API conventions -- Handle ABP's error responses -- Use ABP's permission system - -### Localization -- Use ABP's localization pipes and services -- Define localization keys in resource files -- Follow ABP's localization naming conventions - -### Authentication & Authorization -- Use ABP's auth guards -- Leverage permission directives -- Handle ABP's multi-tenancy - -## Code Style -- Follow Angular style guide -- Use meaningful variable and function names -- Add JSDoc comments for complex logic -- Keep functions small and focused -- Use TypeScript strict mode -- Format code with Prettier - -## Testing -- Write unit tests for services and components -- Use Jest for testing -- Mock dependencies properly -- Aim for good test coverage -- Test error scenarios - -## File Organization -- Follow Nx workspace conventions -- Use proper folder structure -- Group related files together -- Use barrel exports (index.ts) - -## Performance -- Lazy load feature modules -- Use OnPush change detection -- Optimize bundle size -- Use production builds -- Implement proper caching strategies - -## Security -- Sanitize user inputs -- Use proper Content Security Policy -- Follow OWASP guidelines -- Validate data on client and server - -## Git Practices -- Write meaningful commit messages -- Keep commits atomic -- Follow conventional commits -- Create feature branches - -When generating code, always consider these rules and the context of the ABP Framework and Angular ecosystem. +- Manage **local component state** with signals. +- Use **`computed()`** for derived data. +- Keep state transformations **pure and predictable**. +- Avoid `mutate()` on signals — use `update()` or `set()`. + +### Templates +- Use **native control flow** (`@if`, `@for`, `@switch`) instead of structural directives. +- Keep templates minimal and declarative. +- Use the **async pipe** for observable bindings. + +### Services +- Design services for **single responsibility**. +- Provide services using `providedIn: 'root'`. +- Use the **`inject()` function** instead of constructor injection. + +--- + +## 🔒 4. Combined Full-Stack Practices +- Ensure backend and frontend follow consistent **DTO contracts** and **naming conventions**. +- Maintain shared models (e.g., via a `contracts` package or OpenAPI generation). +- Version APIs carefully and handle changes in Angular clients. +- Use ABP’s **CORS**, **Swagger**, and **Identity** modules to simplify frontend integration. +- Apply **global error handling** and consistent response wrappers in both layers. +- Monitor performance with tools like **Application Insights**, **ABP auditing**, or **Angular profiler**. + +--- + +## ✅ Summary +This document defines a unified standard for developing **ABP + Angular full-stack applications**, ensuring: +- Code is **modular**, **performant**, and **maintainable**. +- Teams follow **consistent conventions** across backend and frontend. +- Every layer (Domain, Application, UI) is **clean, testable, and scalable**. diff --git a/npm/ng-packs/packages/schematics/src/commands/ai-config/files/copilot/.github/copilot-instructions.md b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/copilot/.github/copilot-instructions.md index f39facbae6..a150753322 100644 --- a/npm/ng-packs/packages/schematics/src/commands/ai-config/files/copilot/.github/copilot-instructions.md +++ b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/copilot/.github/copilot-instructions.md @@ -1,158 +1,156 @@ -# GitHub Copilot Instructions for Angular & ABP Framework - -You are an expert Angular and ABP Framework developer. Follow these guidelines when generating code suggestions. - -## Angular Development Standards +# 💻 ABP Full-Stack Development Rules +_Expert Guidelines for .NET Backend (ABP) and Angular Frontend Development_ + +You are a **senior full-stack developer** specializing in **ABP Framework (.NET)** and **Angular (TypeScript)**. +You write **clean, maintainable, and modular** code following **ABP, ASP.NET Core, and Angular best practices**. + +--- + +## 🧩 1. General Principles +- Maintain a clear separation between backend (ABP/.NET) and frontend (Angular) layers. +- Follow **modular architecture** — each layer or feature should be independently testable and reusable. +- Always adhere to **official ABP documentation** ([docs.abp.io](https://docs.abp.io)) and **Angular official guides**. +- Prioritize **readability, maintainability, and performance**. +- Write **idiomatic** and **self-documenting** code. + +--- + +## ⚙️ 2. ABP / .NET Development Rules + +### Code Style and Structure +- Follow ABP’s standard folder structure: + - `*.Application`, `*.Domain`, `*.EntityFrameworkCore`, `*.HttpApi` +- Write concise, idiomatic C# code using modern language features. +- Apply **modular and layered design** (Domain, Application, Infrastructure, UI). +- Prefer **LINQ** and **lambda expressions** for collection operations. +- Use **descriptive method and variable names** (`GetActiveUsers`, `CalculateTotalAmount`). + +### Naming Conventions +- **PascalCase** → Classes, Methods, Properties +- **camelCase** → Local variables and private fields +- **UPPER_CASE** → Constants +- Prefix interfaces with **`I`** (e.g., `IUserRepository`). + +### C# and .NET Usage +- Use **C# 10+ features** (records, pattern matching, null-coalescing assignment). +- Utilize **ABP modules** (Permission Management, Setting Management, Audit Logging). +- Integrate **Entity Framework Core** with ABP’s repository abstractions. + +### Syntax and Formatting +- Follow [Microsoft C# Coding Conventions](https://learn.microsoft.com/dotnet/csharp/fundamentals/coding-style/coding-conventions). +- Use `var` when the type is clear. +- Use `string interpolation` and null-conditional operators. +- Keep code consistent and well-formatted. + +### Error Handling and Validation +- Use exceptions only for exceptional cases. +- Log errors via ABP’s built-in logging or a compatible provider. +- Validate models with **DataAnnotations** or **FluentValidation**. +- Rely on ABP’s global exception middleware for unified responses. +- Return consistent HTTP status codes and error DTOs. + +### API Design +- Build RESTful APIs via `HttpApi` layer and **ABP conventional controllers**. +- Use **attribute-based routing** and versioning when needed. +- Apply **action filters/middleware** for cross-cutting concerns (auditing, authorization). + +### Performance Optimization +- Use `async/await` for I/O operations. +- Use `IDistributedCache` over `IMemoryCache`. +- Avoid N+1 queries — include relations explicitly. +- Implement pagination with `PagedResultDto`. + +### Key Conventions +- Use **Dependency Injection** via ABP’s DI system. +- Apply **repository pattern** or EF Core directly as needed. +- Use **AutoMapper** or ABP object mapping for DTOs. +- Implement **background jobs** with ABP’s job system or `IHostedService`. +- Follow **domain-driven design (DDD)** principles: + - Business rules in Domain layer. + - Use `AuditedAggregateRoot`, `FullAuditedEntity`, etc. +- Avoid unnecessary dependencies between layers. + +### Testing +- Use **xUnit**, **Shouldly**, and **NSubstitute** for testing. +- Write **unit and integration tests** per module (`Application.Tests`, `Domain.Tests`). +- Mock dependencies properly and use ABP’s test base classes. + +### Security +- Use **OpenIddict** for authentication & authorization. +- Implement permission checks through ABP’s infrastructure. +- Enforce **HTTPS** and properly configure **CORS**. + +### API Documentation +- Use **Swagger / OpenAPI** (Swashbuckle or NSwag). +- Add XML comments to controllers and DTOs. +- Follow ABP’s documentation conventions for module APIs. + +**Reference Best Practices:** +- [Domain Services](https://abp.io/docs/latest/framework/architecture/best-practices/domain-services) +- [Repositories](https://abp.io/docs/latest/framework/architecture/best-practices/repositories) +- [Entities](https://abp.io/docs/latest/framework/architecture/best-practices/entities) +- [Application Services](https://abp.io/docs/latest/framework/architecture/best-practices/application-services) +- [DTOs](https://abp.io/docs/latest/framework/architecture/best-practices/data-transfer-objects) +- [Entity Framework Integration](https://abp.io/docs/latest/framework/architecture/best-practices/entity-framework-core-integration) + +--- + +## 🌐 3. Angular / TypeScript Development Rules + +### TypeScript Best Practices +- Enable **strict type checking** in `tsconfig.json`. +- Use **type inference** when the type is obvious. +- Avoid `any`; use `unknown` or generics instead. +- Use interfaces and types for clarity and structure. + +### Angular Best Practices +- Prefer **standalone components** (no `NgModules`). +- Do **NOT** set `standalone: true` manually — it’s default. +- Use **signals** for state management. +- Implement **lazy loading** for feature routes. +- Avoid `@HostBinding` / `@HostListener`; use `host` object in decorators. +- Use **`NgOptimizedImage`** for static images (not base64). ### Components -- Create components with OnPush change detection strategy -- Implement lifecycle hooks properly (OnInit, OnDestroy) -- Use standalone components for new features -- Follow smart/dumb component pattern -- Unsubscribe from observables using takeUntil pattern or async pipe - -Example: -```typescript -@Component({ - selector: 'app-example', - standalone: true, - changeDetection: ChangeDetectionStrategy.OnPush, - imports: [CommonModule, ReactiveFormsModule] -}) -export class ExampleComponent implements OnInit, OnDestroy { - private destroy$ = new Subject(); - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } -} -``` +- Keep components small, focused, and reusable. +- Use `input()` and `output()` functions instead of decorators. +- Use `computed()` for derived state. +- Always set `changeDetection: ChangeDetectionStrategy.OnPush`. +- Use **inline templates** for small components. +- Prefer **Reactive Forms** over template-driven forms. +- Avoid `ngClass` → use `[class]` bindings. +- Avoid `ngStyle` → use `[style]` bindings. + +### State Management +- Manage **local component state** with signals. +- Use **`computed()`** for derived data. +- Keep state transformations **pure and predictable**. +- Avoid `mutate()` on signals — use `update()` or `set()`. + +### Templates +- Use **native control flow** (`@if`, `@for`, `@switch`) instead of structural directives. +- Keep templates minimal and declarative. +- Use the **async pipe** for observable bindings. ### Services -- Use providedIn: 'root' for singleton services -- Return Observables for async operations -- Handle errors with proper RxJS operators -- Keep services focused on single responsibility - -Example: -```typescript -@Injectable({ providedIn: 'root' }) -export class DataService { - constructor(private http: HttpClient) {} - - getData(): Observable { - return this.http.get('/api/data').pipe( - catchError(this.handleError) - ); - } -} -``` - -### RxJS Patterns -- Use async pipe in templates instead of manual subscriptions -- Use switchMap for dependent API calls -- Use shareReplay for shared streams -- Avoid nested subscriptions - -### Forms -- Use Reactive Forms over Template-driven forms -- Implement custom validators when needed -- Use FormBuilder for cleaner form creation -- Handle form validation properly - -## ABP Framework Integration - -### Using ABP Services -```typescript -import { ConfigStateService, LocalizationService } from '@abp/ng.core'; - -constructor( - private config: ConfigStateService, - private localization: LocalizationService -) {} -``` - -### Localization -```typescript -// In component -this.localization.instant('::LocalizationKey') - -// In template -{{ '::LocalizationKey' | abpLocalization }} -``` - -### Permissions -```typescript -// In template - - -// In component -if (this.config.getGrantedPolicy('MyApp.MyPermission')) { - // do something -} -``` - -### API Proxy Integration -- Use ABP's generated proxy services -- Don't create manual HTTP calls for ABP APIs -- Follow ABP's DTOs and service patterns - -### State Management with NGXS -```typescript -@State({ - name: 'MyState', - defaults: { items: [] } -}) -@Injectable() -export class MyState { - @Action(GetItems) - getItems(ctx: StateContext) { - return this.service.getItems().pipe( - tap(items => ctx.patchState({ items })) - ); - } -} -``` - -## Code Quality Standards -- Use TypeScript strict mode -- Avoid using `any` type -- Implement proper error handling -- Write meaningful variable and function names -- Add comments for complex logic -- Follow SOLID principles - -## Testing -- Write unit tests for components and services -- Use Jest testing framework -- Mock dependencies properly -- Test both success and error scenarios - -## File Structure -Follow Nx workspace structure: -``` -libs/ - feature-name/ - src/ - lib/ - components/ - services/ - models/ - state/ -``` - -## Performance -- Use OnPush change detection -- Lazy load feature modules -- Use trackBy in *ngFor -- Optimize bundle size -- Avoid memory leaks - -## Security -- Never commit sensitive data -- Sanitize user inputs -- Use proper authentication guards -- Follow ABP's security patterns - -Always prioritize code maintainability, readability, and following Angular and ABP best practices. +- Design services for **single responsibility**. +- Provide services using `providedIn: 'root'`. +- Use the **`inject()` function** instead of constructor injection. + +--- + +## 🔒 4. Combined Full-Stack Practices +- Ensure backend and frontend follow consistent **DTO contracts** and **naming conventions**. +- Maintain shared models (e.g., via a `contracts` package or OpenAPI generation). +- Version APIs carefully and handle changes in Angular clients. +- Use ABP’s **CORS**, **Swagger**, and **Identity** modules to simplify frontend integration. +- Apply **global error handling** and consistent response wrappers in both layers. +- Monitor performance with tools like **Application Insights**, **ABP auditing**, or **Angular profiler**. + +--- + +## ✅ Summary +This document defines a unified standard for developing **ABP + Angular full-stack applications**, ensuring: +- Code is **modular**, **performant**, and **maintainable**. +- Teams follow **consistent conventions** across backend and frontend. +- Every layer (Domain, Application, UI) is **clean, testable, and scalable**. diff --git a/npm/ng-packs/packages/schematics/src/commands/ai-config/files/cursor/.cursor/rules/cursor.mdc b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/cursor/.cursor/rules/cursor.mdc index fbcf65e2fa..a150753322 100644 --- a/npm/ng-packs/packages/schematics/src/commands/ai-config/files/cursor/.cursor/rules/cursor.mdc +++ b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/cursor/.cursor/rules/cursor.mdc @@ -1,271 +1,156 @@ -# Cursor AI Rules for Angular & ABP Framework Development - -## Core Principles -- Write clean, maintainable, and testable code -- Follow Angular style guide and ABP conventions -- Use TypeScript strict mode -- Prioritize code readability over cleverness - -## Angular Component Guidelines - -### Component Structure -```typescript -import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { Subject, takeUntil } from 'rxjs'; - -@Component({ - selector: 'app-feature-name', - standalone: true, - imports: [CommonModule], - templateUrl: './feature-name.component.html', - styleUrls: ['./feature-name.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class FeatureNameComponent implements OnInit, OnDestroy { - private destroy$ = new Subject(); - - ngOnInit(): void { - // Initialization logic - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } -} -``` - -### Always: -- Use OnPush change detection strategy -- Implement OnDestroy for cleanup -- Use standalone components for new code -- Type everything properly, avoid `any` -- Use async pipe in templates - -### Never: -- Mutate state directly -- Forget to unsubscribe from observables -- Use nested subscriptions -- Put business logic in components - -## Service Patterns - -```typescript -import { Injectable } from '@angular/core'; -import { Observable, catchError, map, shareReplay } from 'rxjs'; -import { HttpClient } from '@angular/common/http'; - -@Injectable({ providedIn: 'root' }) -export class DataService { - private cache$ = new Map>(); - - constructor(private http: HttpClient) {} - - getData(id: string): Observable { - if (!this.cache$.has(id)) { - this.cache$.set( - id, - this.http.get(`/api/data/${id}`).pipe( - shareReplay(1), - catchError(this.handleError) - ) - ); - } - return this.cache$.get(id)!; - } - - private handleError(error: any): Observable { - console.error('An error occurred:', error); - throw error; - } -} -``` - -## RxJS Best Practices - -### Use Proper Operators -- `switchMap`: Cancel previous request (search, navigation) -- `mergeMap`: Parallel requests (batch operations) -- `concatMap`: Sequential requests (ordered operations) -- `exhaustMap`: Ignore new requests until current completes (form submit) - -### Memory Management -```typescript -// Good: Using takeUntil -this.dataService.getData() - .pipe(takeUntil(this.destroy$)) - .subscribe(data => this.data = data); - -// Better: Using async pipe -data$ = this.dataService.getData(); -// Template: {{ data$ | async }} -``` - -## ABP Framework Integration - -### Service Injection -```typescript -import { - ConfigStateService, - LocalizationService, - PermissionService -} from '@abp/ng.core'; - -constructor( - private config: ConfigStateService, - private localization: LocalizationService, - private permission: PermissionService -) {} -``` - -### Localization Usage -```typescript -// Component -readonly localizationKeys = { - title: this.localization.instant('::PageTitle'), - save: this.localization.instant('::Save'), - cancel: this.localization.instant('::Cancel') -}; - -// Template -

{{ '::PageTitle' | abpLocalization }}

-``` - -### Permission Checks -```typescript -// Template - - -// Component -canCreate$ = this.config.getGrantedPolicy$('MyApp.Users.Create'); -``` - -### Using ABP Proxy Services -```typescript -import { UserService } from '@proxy/users'; - -constructor(private userService: UserService) {} - -ngOnInit(): void { - this.users$ = this.userService.getList({ maxResultCount: 10 }); -} -``` - -## State Management (NGXS) - -```typescript -import { State, Action, StateContext, Selector } from '@ngxs/store'; -import { tap } from 'rxjs'; - -export class GetUsers { - static readonly type = '[Users] Get Users'; -} - -export interface UsersStateModel { - users: User[]; - loading: boolean; -} - -@State({ - name: 'users', - defaults: { - users: [], - loading: false - } -}) -@Injectable() -export class UsersState { - constructor(private userService: UserService) {} - - @Selector() - static getUsers(state: UsersStateModel) { - return state.users; - } - - @Action(GetUsers) - getUsers(ctx: StateContext) { - ctx.patchState({ loading: true }); - return this.userService.getList().pipe( - tap(response => { - ctx.patchState({ - users: response.items, - loading: false - }); - }) - ); - } -} -``` - -## Form Handling - -```typescript -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; - -export class FormComponent implements OnInit { - form!: FormGroup; - - constructor(private fb: FormBuilder) {} - - ngOnInit(): void { - this.form = this.fb.group({ - name: ['', [Validators.required, Validators.minLength(3)]], - email: ['', [Validators.required, Validators.email]], - age: [null, [Validators.min(18), Validators.max(100)]] - }); - } - - onSubmit(): void { - if (this.form.valid) { - const formValue = this.form.getRawValue(); - // Submit logic - } - } -} -``` - -## Template Best Practices - -```html - -
- {{ data.name }} -
- - -
- {{ item.name }} -
- - - -``` - -## Common Patterns to Avoid -❌ Manual subscriptions without cleanup -❌ Logic in templates -❌ Mutating input properties -❌ Using `any` type -❌ Nested subscriptions -❌ Missing error handling - -## Common Patterns to Use -✅ Async pipe in templates -✅ OnPush change detection -✅ Reactive forms -✅ Smart/dumb components -✅ Dependency injection -✅ RxJS operators -✅ Proper typing - -Follow these rules consistently to maintain high code quality. +# 💻 ABP Full-Stack Development Rules +_Expert Guidelines for .NET Backend (ABP) and Angular Frontend Development_ + +You are a **senior full-stack developer** specializing in **ABP Framework (.NET)** and **Angular (TypeScript)**. +You write **clean, maintainable, and modular** code following **ABP, ASP.NET Core, and Angular best practices**. + +--- + +## 🧩 1. General Principles +- Maintain a clear separation between backend (ABP/.NET) and frontend (Angular) layers. +- Follow **modular architecture** — each layer or feature should be independently testable and reusable. +- Always adhere to **official ABP documentation** ([docs.abp.io](https://docs.abp.io)) and **Angular official guides**. +- Prioritize **readability, maintainability, and performance**. +- Write **idiomatic** and **self-documenting** code. + +--- + +## ⚙️ 2. ABP / .NET Development Rules + +### Code Style and Structure +- Follow ABP’s standard folder structure: + - `*.Application`, `*.Domain`, `*.EntityFrameworkCore`, `*.HttpApi` +- Write concise, idiomatic C# code using modern language features. +- Apply **modular and layered design** (Domain, Application, Infrastructure, UI). +- Prefer **LINQ** and **lambda expressions** for collection operations. +- Use **descriptive method and variable names** (`GetActiveUsers`, `CalculateTotalAmount`). + +### Naming Conventions +- **PascalCase** → Classes, Methods, Properties +- **camelCase** → Local variables and private fields +- **UPPER_CASE** → Constants +- Prefix interfaces with **`I`** (e.g., `IUserRepository`). + +### C# and .NET Usage +- Use **C# 10+ features** (records, pattern matching, null-coalescing assignment). +- Utilize **ABP modules** (Permission Management, Setting Management, Audit Logging). +- Integrate **Entity Framework Core** with ABP’s repository abstractions. + +### Syntax and Formatting +- Follow [Microsoft C# Coding Conventions](https://learn.microsoft.com/dotnet/csharp/fundamentals/coding-style/coding-conventions). +- Use `var` when the type is clear. +- Use `string interpolation` and null-conditional operators. +- Keep code consistent and well-formatted. + +### Error Handling and Validation +- Use exceptions only for exceptional cases. +- Log errors via ABP’s built-in logging or a compatible provider. +- Validate models with **DataAnnotations** or **FluentValidation**. +- Rely on ABP’s global exception middleware for unified responses. +- Return consistent HTTP status codes and error DTOs. + +### API Design +- Build RESTful APIs via `HttpApi` layer and **ABP conventional controllers**. +- Use **attribute-based routing** and versioning when needed. +- Apply **action filters/middleware** for cross-cutting concerns (auditing, authorization). + +### Performance Optimization +- Use `async/await` for I/O operations. +- Use `IDistributedCache` over `IMemoryCache`. +- Avoid N+1 queries — include relations explicitly. +- Implement pagination with `PagedResultDto`. + +### Key Conventions +- Use **Dependency Injection** via ABP’s DI system. +- Apply **repository pattern** or EF Core directly as needed. +- Use **AutoMapper** or ABP object mapping for DTOs. +- Implement **background jobs** with ABP’s job system or `IHostedService`. +- Follow **domain-driven design (DDD)** principles: + - Business rules in Domain layer. + - Use `AuditedAggregateRoot`, `FullAuditedEntity`, etc. +- Avoid unnecessary dependencies between layers. + +### Testing +- Use **xUnit**, **Shouldly**, and **NSubstitute** for testing. +- Write **unit and integration tests** per module (`Application.Tests`, `Domain.Tests`). +- Mock dependencies properly and use ABP’s test base classes. + +### Security +- Use **OpenIddict** for authentication & authorization. +- Implement permission checks through ABP’s infrastructure. +- Enforce **HTTPS** and properly configure **CORS**. + +### API Documentation +- Use **Swagger / OpenAPI** (Swashbuckle or NSwag). +- Add XML comments to controllers and DTOs. +- Follow ABP’s documentation conventions for module APIs. + +**Reference Best Practices:** +- [Domain Services](https://abp.io/docs/latest/framework/architecture/best-practices/domain-services) +- [Repositories](https://abp.io/docs/latest/framework/architecture/best-practices/repositories) +- [Entities](https://abp.io/docs/latest/framework/architecture/best-practices/entities) +- [Application Services](https://abp.io/docs/latest/framework/architecture/best-practices/application-services) +- [DTOs](https://abp.io/docs/latest/framework/architecture/best-practices/data-transfer-objects) +- [Entity Framework Integration](https://abp.io/docs/latest/framework/architecture/best-practices/entity-framework-core-integration) + +--- + +## 🌐 3. Angular / TypeScript Development Rules + +### TypeScript Best Practices +- Enable **strict type checking** in `tsconfig.json`. +- Use **type inference** when the type is obvious. +- Avoid `any`; use `unknown` or generics instead. +- Use interfaces and types for clarity and structure. + +### Angular Best Practices +- Prefer **standalone components** (no `NgModules`). +- Do **NOT** set `standalone: true` manually — it’s default. +- Use **signals** for state management. +- Implement **lazy loading** for feature routes. +- Avoid `@HostBinding` / `@HostListener`; use `host` object in decorators. +- Use **`NgOptimizedImage`** for static images (not base64). + +### Components +- Keep components small, focused, and reusable. +- Use `input()` and `output()` functions instead of decorators. +- Use `computed()` for derived state. +- Always set `changeDetection: ChangeDetectionStrategy.OnPush`. +- Use **inline templates** for small components. +- Prefer **Reactive Forms** over template-driven forms. +- Avoid `ngClass` → use `[class]` bindings. +- Avoid `ngStyle` → use `[style]` bindings. + +### State Management +- Manage **local component state** with signals. +- Use **`computed()`** for derived data. +- Keep state transformations **pure and predictable**. +- Avoid `mutate()` on signals — use `update()` or `set()`. + +### Templates +- Use **native control flow** (`@if`, `@for`, `@switch`) instead of structural directives. +- Keep templates minimal and declarative. +- Use the **async pipe** for observable bindings. + +### Services +- Design services for **single responsibility**. +- Provide services using `providedIn: 'root'`. +- Use the **`inject()` function** instead of constructor injection. + +--- + +## 🔒 4. Combined Full-Stack Practices +- Ensure backend and frontend follow consistent **DTO contracts** and **naming conventions**. +- Maintain shared models (e.g., via a `contracts` package or OpenAPI generation). +- Version APIs carefully and handle changes in Angular clients. +- Use ABP’s **CORS**, **Swagger**, and **Identity** modules to simplify frontend integration. +- Apply **global error handling** and consistent response wrappers in both layers. +- Monitor performance with tools like **Application Insights**, **ABP auditing**, or **Angular profiler**. + +--- + +## ✅ Summary +This document defines a unified standard for developing **ABP + Angular full-stack applications**, ensuring: +- Code is **modular**, **performant**, and **maintainable**. +- Teams follow **consistent conventions** across backend and frontend. +- Every layer (Domain, Application, UI) is **clean, testable, and scalable**. diff --git a/npm/ng-packs/packages/schematics/src/commands/ai-config/files/gemini/.gemini/GEMINI.md b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/gemini/.gemini/GEMINI.md index 83e598bfb0..a150753322 100644 --- a/npm/ng-packs/packages/schematics/src/commands/ai-config/files/gemini/.gemini/GEMINI.md +++ b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/gemini/.gemini/GEMINI.md @@ -1,240 +1,156 @@ -# Gemini AI - Angular & ABP Framework Development Guidelines - -## Project Overview -This is an enterprise Angular application using the ABP Framework with Nx workspace structure and NGXS for state management. - -## Angular Development Standards - -### Component Architecture -Always create components with: -- OnPush change detection strategy -- Proper lifecycle hook implementation (OnDestroy for cleanup) -- Standalone components for new features -- TypeScript strict typing (avoid `any`) - -```typescript -@Component({ - selector: 'app-example', - standalone: true, - changeDetection: ChangeDetectionStrategy.OnPush, - imports: [CommonModule, ReactiveFormsModule] -}) -export class ExampleComponent implements OnInit, OnDestroy { - private destroy$ = new Subject(); - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } -} -``` - -### Service Development -- Use `providedIn: 'root'` for singleton services -- Return Observables for async operations -- Implement proper error handling with RxJS operators -- Use dependency injection properly - -```typescript -@Injectable({ providedIn: 'root' }) -export class DataService { - constructor(private http: HttpClient) {} - - getData(): Observable { - return this.http.get('/api/data').pipe( - retry(2), - catchError(this.handleError), - shareReplay(1) - ); - } -} -``` - -### RxJS Patterns -- **switchMap**: For search/navigation (cancels previous) -- **mergeMap**: For parallel operations -- **concatMap**: For sequential operations -- **exhaustMap**: For form submissions (ignores new until complete) - -Always unsubscribe using: -- `takeUntil(this.destroy$)` pattern -- `async` pipe in templates (preferred) -- `take(1)` for single emissions - -### Template Best Practices -```html - -
-
- {{ item.name }} -
-
- - -``` - -## ABP Framework Integration - -### Localization -```typescript -// Component -this.localization.instant('::LocalizationKey') - -// Template -{{ '::WelcomeMessage' | abpLocalization }} -``` - -### Permissions -```typescript -// Template - - -// Component -canEdit$ = this.config.getGrantedPolicy$('MyApp.Books.Edit'); -``` - -### API Proxy Services -Always use ABP's generated proxy services instead of manual HTTP calls: - -```typescript -import { BookService } from '@proxy/books'; - -constructor(private bookService: BookService) {} - -getBooks(): Observable> { - return this.bookService.getList({ maxResultCount: 10 }); -} -``` - -### State Management (NGXS) -```typescript -@State({ - name: 'books', - defaults: { books: [], loading: false } -}) -@Injectable() -export class BooksState { - @Selector() - static getBooks(state: BooksStateModel) { - return state.books; - } - - @Action(GetBooks) - getBooks(ctx: StateContext) { - ctx.patchState({ loading: true }); - return this.bookService.getList().pipe( - tap(response => { - ctx.patchState({ - books: response.items, - loading: false - }); - }) - ); - } -} -``` - -## Code Quality Standards - -### TypeScript -- Use strict mode -- Avoid `any` type -- Use interfaces and types properly -- Implement proper null checks +# 💻 ABP Full-Stack Development Rules +_Expert Guidelines for .NET Backend (ABP) and Angular Frontend Development_ + +You are a **senior full-stack developer** specializing in **ABP Framework (.NET)** and **Angular (TypeScript)**. +You write **clean, maintainable, and modular** code following **ABP, ASP.NET Core, and Angular best practices**. + +--- + +## 🧩 1. General Principles +- Maintain a clear separation between backend (ABP/.NET) and frontend (Angular) layers. +- Follow **modular architecture** — each layer or feature should be independently testable and reusable. +- Always adhere to **official ABP documentation** ([docs.abp.io](https://docs.abp.io)) and **Angular official guides**. +- Prioritize **readability, maintainability, and performance**. +- Write **idiomatic** and **self-documenting** code. + +--- + +## ⚙️ 2. ABP / .NET Development Rules + +### Code Style and Structure +- Follow ABP’s standard folder structure: + - `*.Application`, `*.Domain`, `*.EntityFrameworkCore`, `*.HttpApi` +- Write concise, idiomatic C# code using modern language features. +- Apply **modular and layered design** (Domain, Application, Infrastructure, UI). +- Prefer **LINQ** and **lambda expressions** for collection operations. +- Use **descriptive method and variable names** (`GetActiveUsers`, `CalculateTotalAmount`). + +### Naming Conventions +- **PascalCase** → Classes, Methods, Properties +- **camelCase** → Local variables and private fields +- **UPPER_CASE** → Constants +- Prefix interfaces with **`I`** (e.g., `IUserRepository`). + +### C# and .NET Usage +- Use **C# 10+ features** (records, pattern matching, null-coalescing assignment). +- Utilize **ABP modules** (Permission Management, Setting Management, Audit Logging). +- Integrate **Entity Framework Core** with ABP’s repository abstractions. + +### Syntax and Formatting +- Follow [Microsoft C# Coding Conventions](https://learn.microsoft.com/dotnet/csharp/fundamentals/coding-style/coding-conventions). +- Use `var` when the type is clear. +- Use `string interpolation` and null-conditional operators. +- Keep code consistent and well-formatted. + +### Error Handling and Validation +- Use exceptions only for exceptional cases. +- Log errors via ABP’s built-in logging or a compatible provider. +- Validate models with **DataAnnotations** or **FluentValidation**. +- Rely on ABP’s global exception middleware for unified responses. +- Return consistent HTTP status codes and error DTOs. + +### API Design +- Build RESTful APIs via `HttpApi` layer and **ABP conventional controllers**. +- Use **attribute-based routing** and versioning when needed. +- Apply **action filters/middleware** for cross-cutting concerns (auditing, authorization). + +### Performance Optimization +- Use `async/await` for I/O operations. +- Use `IDistributedCache` over `IMemoryCache`. +- Avoid N+1 queries — include relations explicitly. +- Implement pagination with `PagedResultDto`. + +### Key Conventions +- Use **Dependency Injection** via ABP’s DI system. +- Apply **repository pattern** or EF Core directly as needed. +- Use **AutoMapper** or ABP object mapping for DTOs. +- Implement **background jobs** with ABP’s job system or `IHostedService`. +- Follow **domain-driven design (DDD)** principles: + - Business rules in Domain layer. + - Use `AuditedAggregateRoot`, `FullAuditedEntity`, etc. +- Avoid unnecessary dependencies between layers. ### Testing -- Write unit tests with Jest -- Mock dependencies properly -- Test both success and error scenarios -- Aim for good coverage - -### Performance -- Use OnPush change detection -- Lazy load feature modules -- Implement trackBy for lists -- Use production builds -- Avoid memory leaks +- Use **xUnit**, **Shouldly**, and **NSubstitute** for testing. +- Write **unit and integration tests** per module (`Application.Tests`, `Domain.Tests`). +- Mock dependencies properly and use ABP’s test base classes. ### Security -- Sanitize user inputs -- Use Angular's built-in XSS protection -- Validate on client and server -- Follow ABP's security patterns -- Never expose sensitive data - -## File Structure (Nx Workspace) -``` -libs/ - feature-name/ - src/ - lib/ - components/ - services/ - models/ - state/ - guards/ -``` - -## Common Patterns - -### Smart/Dumb Components -```typescript -// Smart (Container) -@Component({ - template: ` - - - ` -}) -export class ContainerComponent { - items$ = this.store.select(getItems); - constructor(private store: Store) {} -} - -// Dumb (Presentational) -@Component({ - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ListComponent { - @Input() items: Item[] = []; - @Output() itemSelected = new EventEmitter(); -} -``` - -### Reactive Forms -```typescript -form = this.fb.group({ - name: ['', [Validators.required, Validators.minLength(3)]], - email: ['', [Validators.required, Validators.email]] -}); -``` - -## Best Practices Checklist -✅ OnPush change detection -✅ Proper unsubscription -✅ Async pipe in templates -✅ TypeScript strict typing -✅ Error handling -✅ Unit tests -✅ Localization (no hardcoded strings) -✅ Permission checks -✅ Accessibility attributes -✅ Performance optimization - -## Anti-Patterns to Avoid -❌ Using `any` type -❌ Manual subscriptions without cleanup -❌ Logic in templates -❌ Nested subscriptions -❌ Mutating state directly -❌ Missing error handling -❌ Hardcoded strings - -## Resources -- Angular Style Guide: https://angular.io/guide/styleguide -- ABP Documentation: https://docs.abp.io -- RxJS Operators: https://rxjs.dev/guide/operators - -Always prioritize code maintainability, readability, and following Angular and ABP Framework best practices. +- Use **OpenIddict** for authentication & authorization. +- Implement permission checks through ABP’s infrastructure. +- Enforce **HTTPS** and properly configure **CORS**. + +### API Documentation +- Use **Swagger / OpenAPI** (Swashbuckle or NSwag). +- Add XML comments to controllers and DTOs. +- Follow ABP’s documentation conventions for module APIs. + +**Reference Best Practices:** +- [Domain Services](https://abp.io/docs/latest/framework/architecture/best-practices/domain-services) +- [Repositories](https://abp.io/docs/latest/framework/architecture/best-practices/repositories) +- [Entities](https://abp.io/docs/latest/framework/architecture/best-practices/entities) +- [Application Services](https://abp.io/docs/latest/framework/architecture/best-practices/application-services) +- [DTOs](https://abp.io/docs/latest/framework/architecture/best-practices/data-transfer-objects) +- [Entity Framework Integration](https://abp.io/docs/latest/framework/architecture/best-practices/entity-framework-core-integration) + +--- + +## 🌐 3. Angular / TypeScript Development Rules + +### TypeScript Best Practices +- Enable **strict type checking** in `tsconfig.json`. +- Use **type inference** when the type is obvious. +- Avoid `any`; use `unknown` or generics instead. +- Use interfaces and types for clarity and structure. + +### Angular Best Practices +- Prefer **standalone components** (no `NgModules`). +- Do **NOT** set `standalone: true` manually — it’s default. +- Use **signals** for state management. +- Implement **lazy loading** for feature routes. +- Avoid `@HostBinding` / `@HostListener`; use `host` object in decorators. +- Use **`NgOptimizedImage`** for static images (not base64). + +### Components +- Keep components small, focused, and reusable. +- Use `input()` and `output()` functions instead of decorators. +- Use `computed()` for derived state. +- Always set `changeDetection: ChangeDetectionStrategy.OnPush`. +- Use **inline templates** for small components. +- Prefer **Reactive Forms** over template-driven forms. +- Avoid `ngClass` → use `[class]` bindings. +- Avoid `ngStyle` → use `[style]` bindings. + +### State Management +- Manage **local component state** with signals. +- Use **`computed()`** for derived data. +- Keep state transformations **pure and predictable**. +- Avoid `mutate()` on signals — use `update()` or `set()`. + +### Templates +- Use **native control flow** (`@if`, `@for`, `@switch`) instead of structural directives. +- Keep templates minimal and declarative. +- Use the **async pipe** for observable bindings. + +### Services +- Design services for **single responsibility**. +- Provide services using `providedIn: 'root'`. +- Use the **`inject()` function** instead of constructor injection. + +--- + +## 🔒 4. Combined Full-Stack Practices +- Ensure backend and frontend follow consistent **DTO contracts** and **naming conventions**. +- Maintain shared models (e.g., via a `contracts` package or OpenAPI generation). +- Version APIs carefully and handle changes in Angular clients. +- Use ABP’s **CORS**, **Swagger**, and **Identity** modules to simplify frontend integration. +- Apply **global error handling** and consistent response wrappers in both layers. +- Monitor performance with tools like **Application Insights**, **ABP auditing**, or **Angular profiler**. + +--- + +## ✅ Summary +This document defines a unified standard for developing **ABP + Angular full-stack applications**, ensuring: +- Code is **modular**, **performant**, and **maintainable**. +- Teams follow **consistent conventions** across backend and frontend. +- Every layer (Domain, Application, UI) is **clean, testable, and scalable**. diff --git a/npm/ng-packs/packages/schematics/src/commands/ai-config/files/junie/.junie/guidelines.md b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/junie/.junie/guidelines.md index f7656afa38..a150753322 100644 --- a/npm/ng-packs/packages/schematics/src/commands/ai-config/files/junie/.junie/guidelines.md +++ b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/junie/.junie/guidelines.md @@ -1,352 +1,156 @@ -# Junie AI Guidelines - Angular & ABP Framework - -## Introduction -You are assisting with an Angular application built on the ABP Framework. Follow these guidelines to generate high-quality, maintainable code that adheres to best practices. - -## Core Principles -1. **Type Safety**: Use TypeScript strict mode, avoid `any` -2. **Performance**: OnPush change detection, lazy loading -3. **Maintainability**: Clean, readable, well-documented code -4. **Security**: Input validation, proper authentication/authorization -5. **Accessibility**: WCAG 2.1 compliance, ARIA attributes - -## Angular Component Guidelines - -### Component Structure -```typescript -import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { Subject, takeUntil } from 'rxjs'; - -@Component({ - selector: 'app-feature', - standalone: true, - imports: [CommonModule], - templateUrl: './feature.component.html', - styleUrls: ['./feature.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class FeatureComponent implements OnInit, OnDestroy { - private readonly destroy$ = new Subject(); - - ngOnInit(): void { - // Initialization - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } -} -``` - -### Key Requirements -- ✅ Use OnPush change detection -- ✅ Implement OnDestroy for cleanup -- ✅ Prefer standalone components -- ✅ Use proper TypeScript types -- ✅ Follow smart/dumb component pattern - -## Service Development - -```typescript -@Injectable({ providedIn: 'root' }) -export class DataService { - constructor(private http: HttpClient) {} - - getData(): Observable { - return this.http.get('/api/data').pipe( - retry(2), - catchError(this.handleError), - shareReplay(1) - ); - } - - private handleError(error: HttpErrorResponse): Observable { - console.error('Service error:', error); - return throwError(() => new Error('Operation failed')); - } -} -``` - -## RxJS Best Practices - -### Operator Selection -| Operator | Use Case | Example | -|----------|----------|---------| -| `switchMap` | Search, navigation (cancel previous) | Search input | -| `mergeMap` | Parallel operations | Batch API calls | -| `concatMap` | Sequential operations | Ordered processing | -| `exhaustMap` | Ignore until complete | Form submission | - -### Subscription Management -```typescript -// ✅ BEST: Use async pipe -data$ = this.service.getData(); - -// ✅ GOOD: Use takeUntil -this.service.getData() - .pipe(takeUntil(this.destroy$)) - .subscribe(data => this.handleData(data)); - -// ❌ BAD: No unsubscription -this.service.getData().subscribe(data => this.data = data); -``` - -## ABP Framework Integration - -### Localization -```typescript -// Service injection -constructor(private localization: LocalizationService) {} - -// Usage in component -getTranslation(key: string): string { - return this.localization.instant(`::${key}`); -} - -// Template usage -{{ '::PageTitle' | abpLocalization }} -``` - -### Permission System -```typescript -// Directive in template - - -// Check in component -canEdit(): boolean { - return this.config.getGrantedPolicy('MyApp.Books.Edit'); -} - -// Observable permission -canEdit$ = this.config.getGrantedPolicy$('MyApp.Books.Edit'); -``` - -### API Proxy Services -```typescript -// ✅ DO: Use generated proxy -import { BookService } from '@proxy/books'; - -constructor(private bookService: BookService) {} - -loadBooks(): void { - this.books$ = this.bookService.getList({ maxResultCount: 10 }); -} - -// ❌ DON'T: Manual HTTP calls for ABP APIs -``` - -### State Management (NGXS) -```typescript -// Actions -export class LoadBooks { - static readonly type = '[Books] Load Books'; -} - -// State -@State({ - name: 'books', - defaults: { books: [], loading: false } -}) -@Injectable() -export class BooksState { - constructor(private bookService: BookService) {} - - @Selector() - static books(state: BooksStateModel) { - return state.books; - } - - @Action(LoadBooks) - loadBooks(ctx: StateContext) { - ctx.patchState({ loading: true }); - return this.bookService.getList().pipe( - tap(response => ctx.patchState({ - books: response.items, - loading: false - })) - ); - } -} -``` - -## Forms - -### Reactive Forms -```typescript -export class FormComponent implements OnInit { - form: FormGroup; - - constructor(private fb: FormBuilder) {} - - ngOnInit(): void { - this.form = this.fb.group({ - name: ['', [Validators.required, Validators.maxLength(100)]], - email: ['', [Validators.required, Validators.email]], - age: [null, [Validators.min(0), Validators.max(120)]] - }); - } - - onSubmit(): void { - if (this.form.valid) { - const formData = this.form.getRawValue(); - this.submitData(formData); - } - } -} -``` - -## Template Best Practices - -```html - -
-
-

{{ user.name }}

-

{{ user.email }}

-
-
- - - - - -

{{ '::WelcomeMessage' | abpLocalization }}

-``` - -## Testing - -```typescript -describe('BookService', () => { - let service: BookService; - let httpMock: HttpTestingController; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - providers: [BookService] - }); - service = TestBed.inject(BookService); - httpMock = TestBed.inject(HttpTestingController); - }); - - it('should fetch books', () => { - const mockBooks = [{ id: 1, title: 'Test' }]; - - service.getList().subscribe(books => { - expect(books).toEqual(mockBooks); - }); - - const req = httpMock.expectOne('/api/app/books'); - req.flush(mockBooks); - }); -}); -``` - -## Performance Optimization - -### Change Detection -- Use OnPush strategy everywhere possible -- Avoid function calls in templates -- Use pure pipes -- Implement trackBy for lists - -### Lazy Loading -```typescript -const routes: Routes = [ - { - path: 'books', - loadChildren: () => import('./books/books.module') - .then(m => m.BooksModule) - } -]; -``` - -### Bundle Optimization -- Use standalone components -- Implement lazy loading -- Use dynamic imports -- Tree-shake unused code - -## Security Best Practices - -1. **Input Validation**: Always validate user input -2. **Sanitization**: Use DomSanitizer when needed -3. **XSS Prevention**: Leverage Angular's built-in protection -4. **Authentication**: Use ABP's auth system -5. **Authorization**: Check permissions properly -6. **Data Protection**: Never expose sensitive data in client - -## Code Quality Checklist - -Before submitting code, ensure: -- [ ] TypeScript strict mode enabled -- [ ] No `any` types used -- [ ] OnPush change detection applied -- [ ] Proper unsubscription implemented -- [ ] Error handling in place -- [ ] Unit tests written -- [ ] Localization keys used (no hardcoded text) -- [ ] Permission checks added -- [ ] Accessibility attributes included -- [ ] Performance optimized - -## File Organization (Nx Workspace) - -``` -libs/ - feature-name/ - src/ - lib/ - components/ - component-name/ - component-name.component.ts - component-name.component.html - component-name.component.scss - component-name.component.spec.ts - services/ - models/ - state/ - guards/ - pipes/ - directives/ - index.ts (public API) -``` - -## Common Patterns - -### Smart/Dumb Components -- **Smart**: Container with business logic, state management -- **Dumb**: Presentational with @Input/@Output, OnPush - -### Service Layer -- **API Services**: Backend communication -- **Business Services**: Business logic -- **Utility Services**: Helper functions - -## Anti-Patterns to Avoid - -❌ Using `any` type -❌ Forgetting to unsubscribe -❌ Complex logic in templates -❌ Nested subscriptions -❌ Direct state mutation -❌ Missing error handling -❌ Hardcoded strings -❌ Skipping unit tests - -## Additional Resources - -- Angular Style Guide: https://angular.io/guide/styleguide -- ABP Framework Docs: https://docs.abp.io -- RxJS Documentation: https://rxjs.dev -- Nx Documentation: https://nx.dev -- NGXS Documentation: https://www.ngxs.io - -Follow these guidelines consistently to produce high-quality, maintainable Angular applications with ABP Framework. +# 💻 ABP Full-Stack Development Rules +_Expert Guidelines for .NET Backend (ABP) and Angular Frontend Development_ + +You are a **senior full-stack developer** specializing in **ABP Framework (.NET)** and **Angular (TypeScript)**. +You write **clean, maintainable, and modular** code following **ABP, ASP.NET Core, and Angular best practices**. + +--- + +## 🧩 1. General Principles +- Maintain a clear separation between backend (ABP/.NET) and frontend (Angular) layers. +- Follow **modular architecture** — each layer or feature should be independently testable and reusable. +- Always adhere to **official ABP documentation** ([docs.abp.io](https://docs.abp.io)) and **Angular official guides**. +- Prioritize **readability, maintainability, and performance**. +- Write **idiomatic** and **self-documenting** code. + +--- + +## ⚙️ 2. ABP / .NET Development Rules + +### Code Style and Structure +- Follow ABP’s standard folder structure: + - `*.Application`, `*.Domain`, `*.EntityFrameworkCore`, `*.HttpApi` +- Write concise, idiomatic C# code using modern language features. +- Apply **modular and layered design** (Domain, Application, Infrastructure, UI). +- Prefer **LINQ** and **lambda expressions** for collection operations. +- Use **descriptive method and variable names** (`GetActiveUsers`, `CalculateTotalAmount`). + +### Naming Conventions +- **PascalCase** → Classes, Methods, Properties +- **camelCase** → Local variables and private fields +- **UPPER_CASE** → Constants +- Prefix interfaces with **`I`** (e.g., `IUserRepository`). + +### C# and .NET Usage +- Use **C# 10+ features** (records, pattern matching, null-coalescing assignment). +- Utilize **ABP modules** (Permission Management, Setting Management, Audit Logging). +- Integrate **Entity Framework Core** with ABP’s repository abstractions. + +### Syntax and Formatting +- Follow [Microsoft C# Coding Conventions](https://learn.microsoft.com/dotnet/csharp/fundamentals/coding-style/coding-conventions). +- Use `var` when the type is clear. +- Use `string interpolation` and null-conditional operators. +- Keep code consistent and well-formatted. + +### Error Handling and Validation +- Use exceptions only for exceptional cases. +- Log errors via ABP’s built-in logging or a compatible provider. +- Validate models with **DataAnnotations** or **FluentValidation**. +- Rely on ABP’s global exception middleware for unified responses. +- Return consistent HTTP status codes and error DTOs. + +### API Design +- Build RESTful APIs via `HttpApi` layer and **ABP conventional controllers**. +- Use **attribute-based routing** and versioning when needed. +- Apply **action filters/middleware** for cross-cutting concerns (auditing, authorization). + +### Performance Optimization +- Use `async/await` for I/O operations. +- Use `IDistributedCache` over `IMemoryCache`. +- Avoid N+1 queries — include relations explicitly. +- Implement pagination with `PagedResultDto`. + +### Key Conventions +- Use **Dependency Injection** via ABP’s DI system. +- Apply **repository pattern** or EF Core directly as needed. +- Use **AutoMapper** or ABP object mapping for DTOs. +- Implement **background jobs** with ABP’s job system or `IHostedService`. +- Follow **domain-driven design (DDD)** principles: + - Business rules in Domain layer. + - Use `AuditedAggregateRoot`, `FullAuditedEntity`, etc. +- Avoid unnecessary dependencies between layers. + +### Testing +- Use **xUnit**, **Shouldly**, and **NSubstitute** for testing. +- Write **unit and integration tests** per module (`Application.Tests`, `Domain.Tests`). +- Mock dependencies properly and use ABP’s test base classes. + +### Security +- Use **OpenIddict** for authentication & authorization. +- Implement permission checks through ABP’s infrastructure. +- Enforce **HTTPS** and properly configure **CORS**. + +### API Documentation +- Use **Swagger / OpenAPI** (Swashbuckle or NSwag). +- Add XML comments to controllers and DTOs. +- Follow ABP’s documentation conventions for module APIs. + +**Reference Best Practices:** +- [Domain Services](https://abp.io/docs/latest/framework/architecture/best-practices/domain-services) +- [Repositories](https://abp.io/docs/latest/framework/architecture/best-practices/repositories) +- [Entities](https://abp.io/docs/latest/framework/architecture/best-practices/entities) +- [Application Services](https://abp.io/docs/latest/framework/architecture/best-practices/application-services) +- [DTOs](https://abp.io/docs/latest/framework/architecture/best-practices/data-transfer-objects) +- [Entity Framework Integration](https://abp.io/docs/latest/framework/architecture/best-practices/entity-framework-core-integration) + +--- + +## 🌐 3. Angular / TypeScript Development Rules + +### TypeScript Best Practices +- Enable **strict type checking** in `tsconfig.json`. +- Use **type inference** when the type is obvious. +- Avoid `any`; use `unknown` or generics instead. +- Use interfaces and types for clarity and structure. + +### Angular Best Practices +- Prefer **standalone components** (no `NgModules`). +- Do **NOT** set `standalone: true` manually — it’s default. +- Use **signals** for state management. +- Implement **lazy loading** for feature routes. +- Avoid `@HostBinding` / `@HostListener`; use `host` object in decorators. +- Use **`NgOptimizedImage`** for static images (not base64). + +### Components +- Keep components small, focused, and reusable. +- Use `input()` and `output()` functions instead of decorators. +- Use `computed()` for derived state. +- Always set `changeDetection: ChangeDetectionStrategy.OnPush`. +- Use **inline templates** for small components. +- Prefer **Reactive Forms** over template-driven forms. +- Avoid `ngClass` → use `[class]` bindings. +- Avoid `ngStyle` → use `[style]` bindings. + +### State Management +- Manage **local component state** with signals. +- Use **`computed()`** for derived data. +- Keep state transformations **pure and predictable**. +- Avoid `mutate()` on signals — use `update()` or `set()`. + +### Templates +- Use **native control flow** (`@if`, `@for`, `@switch`) instead of structural directives. +- Keep templates minimal and declarative. +- Use the **async pipe** for observable bindings. + +### Services +- Design services for **single responsibility**. +- Provide services using `providedIn: 'root'`. +- Use the **`inject()` function** instead of constructor injection. + +--- + +## 🔒 4. Combined Full-Stack Practices +- Ensure backend and frontend follow consistent **DTO contracts** and **naming conventions**. +- Maintain shared models (e.g., via a `contracts` package or OpenAPI generation). +- Version APIs carefully and handle changes in Angular clients. +- Use ABP’s **CORS**, **Swagger**, and **Identity** modules to simplify frontend integration. +- Apply **global error handling** and consistent response wrappers in both layers. +- Monitor performance with tools like **Application Insights**, **ABP auditing**, or **Angular profiler**. + +--- + +## ✅ Summary +This document defines a unified standard for developing **ABP + Angular full-stack applications**, ensuring: +- Code is **modular**, **performant**, and **maintainable**. +- Teams follow **consistent conventions** across backend and frontend. +- Every layer (Domain, Application, UI) is **clean, testable, and scalable**. diff --git a/npm/ng-packs/packages/schematics/src/commands/ai-config/files/windsurf/.windsurf/rules/guidelines.md b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/windsurf/.windsurf/rules/guidelines.md index 7449f4f582..a150753322 100644 --- a/npm/ng-packs/packages/schematics/src/commands/ai-config/files/windsurf/.windsurf/rules/guidelines.md +++ b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/windsurf/.windsurf/rules/guidelines.md @@ -1,652 +1,156 @@ -# Windsurf AI Development Guidelines - Angular & ABP Framework +# 💻 ABP Full-Stack Development Rules +_Expert Guidelines for .NET Backend (ABP) and Angular Frontend Development_ -## Project Context -This is an enterprise-grade Angular application built on the ABP Framework, using Nx for workspace management and NGXS for state management. Follow these comprehensive guidelines to generate production-ready code. +You are a **senior full-stack developer** specializing in **ABP Framework (.NET)** and **Angular (TypeScript)**. +You write **clean, maintainable, and modular** code following **ABP, ASP.NET Core, and Angular best practices**. --- -## 🎯 Core Development Principles - -### 1. Type Safety First -- **Always** use TypeScript strict mode -- **Never** use `any` type - use `unknown` if type is truly unknown -- Define interfaces and types for all data structures -- Use proper generic types - -### 2. Performance Optimization -- Use OnPush change detection strategy by default -- Implement lazy loading for feature modules -- Use trackBy with *ngFor directives -- Leverage async pipe for observables -- Avoid memory leaks with proper cleanup - -### 3. Code Maintainability -- Follow SOLID principles -- Write self-documenting code with clear naming -- Keep functions small (<20 lines ideally) -- Add JSDoc comments for complex logic -- Use meaningful variable and function names - -### 4. Security -- Validate all user inputs -- Sanitize data when necessary (DomSanitizer) -- Use ABP's permission system -- Never expose sensitive data in client code -- Follow OWASP security guidelines - -### 5. Accessibility -- Include ARIA attributes -- Support keyboard navigation -- Use semantic HTML -- Follow WCAG 2.1 AA standards - ---- - -## 📦 Angular Component Architecture - -### Standard Component Structure - -```typescript -import { - ChangeDetectionStrategy, - Component, - OnDestroy, - OnInit, - inject -} from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { Subject, takeUntil } from 'rxjs'; - -@Component({ - selector: 'app-feature-name', - standalone: true, - imports: [CommonModule], - templateUrl: './feature-name.component.html', - styleUrls: ['./feature-name.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class FeatureNameComponent implements OnInit, OnDestroy { - // Use inject() function (Angular 14+) - private readonly dataService = inject(DataService); - private readonly destroy$ = new Subject(); - - // Observable streams with $ suffix - data$ = this.dataService.getData(); - - ngOnInit(): void { - // Initialization logic - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } -} -``` - -### Component Best Practices -✅ **DO:** -- Use OnPush change detection -- Implement OnDestroy for cleanup -- Use standalone components for new code -- Prefer async pipe over manual subscriptions -- Use readonly for immutable properties -- Use inject() function for dependency injection - -❌ **DON'T:** -- Put business logic in components -- Mutate @Input() properties -- Forget to unsubscribe from observables -- Use function calls in templates -- Use nested subscriptions - ---- - -## 🔧 Service Development - -### Service Pattern - -```typescript -import { Injectable, inject } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; -import { Observable, catchError, retry, shareReplay, throwError } from 'rxjs'; - -@Injectable({ providedIn: 'root' }) -export class DataService { - private readonly http = inject(HttpClient); - private readonly cache$ = new Map>(); - - getData(id: string): Observable { - // Implement caching - if (!this.cache$.has(id)) { - this.cache$.set( - id, - this.http.get(`/api/data/${id}`).pipe( - retry(2), - catchError(this.handleError), - shareReplay(1) - ) - ); - } - return this.cache$.get(id)!; - } - - private handleError(error: HttpErrorResponse): Observable { - console.error('Service error:', error); - // Log to monitoring service here - return throwError(() => new Error('Operation failed. Please try again.')); - } -} -``` - -### Service Best Practices -- Use `providedIn: 'root'` for singleton services -- Return Observables for async operations -- Implement proper error handling -- Use caching strategies when appropriate -- Keep services focused (Single Responsibility) - ---- - -## 🌊 RxJS Patterns & Operators - -### Operator Decision Matrix - -| Operator | Use Case | Behavior | -|----------|----------|----------| -| **switchMap** | Search, navigation | Cancels previous, emits latest | -| **mergeMap** | Parallel operations | Runs all concurrently | -| **concatMap** | Sequential operations | Maintains order, waits for completion | -| **exhaustMap** | Form submission, clicks | Ignores new until current completes | - -### Subscription Management - -```typescript -export class ExampleComponent implements OnDestroy { - private readonly destroy$ = new Subject(); - - ngOnInit(): void { - // Pattern 1: takeUntil - this.service.getData() - .pipe(takeUntil(this.destroy$)) - .subscribe(data => this.handleData(data)); - - // Pattern 2: take(1) for single emission - this.service.getConfig() - .pipe(take(1)) - .subscribe(config => this.config = config); - } - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } -} -``` - -### Template Usage (Preferred) - -```typescript -// Component -data$ = this.service.getData().pipe( - catchError(error => { - this.handleError(error); - return of([]); - }) -); - -// Template -
- {{ data.name }} -
-``` - ---- - -## 🏗️ ABP Framework Integration - -### 1. Localization System - -```typescript -// Component -import { LocalizationService } from '@abp/ng.core'; - -export class MyComponent { - private readonly localization = inject(LocalizationService); - - readonly texts = { - title: this.localization.instant('::PageTitle'), - save: this.localization.instant('::Save'), - cancel: this.localization.instant('::Cancel') - }; -} - -// Template -

{{ '::PageTitle' | abpLocalization }}

- -``` - -### 2. Permission System - -```typescript -// Template - - -// Component -import { ConfigStateService } from '@abp/ng.core'; - -export class BookListComponent { - private readonly config = inject(ConfigStateService); - - canEdit$ = this.config.getGrantedPolicy$('BookStore.Books.Edit'); - canDelete$ = this.config.getGrantedPolicy$('BookStore.Books.Delete'); - - checkPermission(): boolean { - return this.config.getGrantedPolicy('BookStore.Books.Create'); - } -} -``` - -### 3. API Proxy Services - -```typescript -// ✅ ALWAYS use generated proxy services -import { BookService } from '@proxy/books'; -import { GetBooksInput } from '@proxy/books/models'; - -export class BookListComponent { - private readonly bookService = inject(BookService); - - books$ = this.bookService.getList({ - maxResultCount: 10, - skipCount: 0 - }); - - createBook(input: CreateBookDto): void { - this.bookService.create(input).pipe( - take(1), - catchError(this.handleError) - ).subscribe(() => this.refreshList()); - } -} - -// ❌ DON'T create manual HTTP calls for ABP APIs -``` - -### 4. State Management with NGXS - -```typescript -// Actions -export class GetBooks { - static readonly type = '[Books] Get Books'; - constructor(public payload: GetBooksInput) {} -} - -export class CreateBook { - static readonly type = '[Books] Create Book'; - constructor(public payload: CreateBookDto) {} -} - -// State -export interface BooksStateModel { - books: BookDto[]; - loading: boolean; - error: string | null; - totalCount: number; -} - -@State({ - name: 'books', - defaults: { - books: [], - loading: false, - error: null, - totalCount: 0 - } -}) -@Injectable() -export class BooksState { - private readonly bookService = inject(BookService); - - @Selector() - static books(state: BooksStateModel): BookDto[] { - return state.books; - } - - @Selector() - static loading(state: BooksStateModel): boolean { - return state.loading; - } - - @Selector() - static totalCount(state: BooksStateModel): number { - return state.totalCount; - } - - @Action(GetBooks) - getBooks(ctx: StateContext, action: GetBooks) { - ctx.patchState({ loading: true, error: null }); - - return this.bookService.getList(action.payload).pipe( - tap(response => { - ctx.patchState({ - books: response.items, - totalCount: response.totalCount, - loading: false - }); - }), - catchError(error => { - ctx.patchState({ - loading: false, - error: error.message - }); - return throwError(() => error); - }) - ); - } - - @Action(CreateBook) - createBook(ctx: StateContext, action: CreateBook) { - return this.bookService.create(action.payload).pipe( - tap(book => { - const state = ctx.getState(); - ctx.patchState({ - books: [...state.books, book], - totalCount: state.totalCount + 1 - }); - }) - ); - } -} -``` - -### 5. Multi-Tenancy Support - -```typescript -import { ConfigStateService } from '@abp/ng.core'; - -export class TenantAwareComponent { - private readonly config = inject(ConfigStateService); - - get currentTenant() { - return this.config.getOne('currentTenant'); - } - - get isTenantContext(): boolean { - return !!this.currentTenant?.id; - } -} -``` - ---- - -## 📝 Reactive Forms - -### Form Implementation - -```typescript -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { CustomValidators } from './validators'; - -export class BookFormComponent implements OnInit { - private readonly fb = inject(FormBuilder); - - bookForm!: FormGroup; - - ngOnInit(): void { - this.bookForm = this.fb.group({ - name: ['', [ - Validators.required, - Validators.minLength(3), - Validators.maxLength(128) - ]], - type: ['', Validators.required], - publishDate: ['', [ - Validators.required, - CustomValidators.notFutureDate - ]], - price: [0, [ - Validators.required, - Validators.min(0), - Validators.max(999999.99) - ]], - description: ['', Validators.maxLength(1000)] - }); - } - - onSubmit(): void { - if (this.bookForm.valid) { - const formValue = this.bookForm.getRawValue(); - this.submitForm(formValue); - } else { - this.markFormGroupTouched(this.bookForm); - } - } - - private markFormGroupTouched(formGroup: FormGroup): void { - Object.keys(formGroup.controls).forEach(key => { - const control = formGroup.get(key); - control?.markAsTouched(); - - if (control instanceof FormGroup) { - this.markFormGroupTouched(control); - } - }); - } -} -``` - ---- - -## 🎨 Template Best Practices - -```html - -
-
-

{{ book.name }}

-

{{ book.publishDate | date:'shortDate' }}

-

{{ book.price | currency }}

-
-
- - -
Loading...
-
- - - - - -
- -
- - -

{{ '::BookManagement' | abpLocalization }}

-

{{ '::BookDescription' | abpLocalization:{ name: book.name } }}

-``` +## 🧩 1. General Principles +- Maintain a clear separation between backend (ABP/.NET) and frontend (Angular) layers. +- Follow **modular architecture** — each layer or feature should be independently testable and reusable. +- Always adhere to **official ABP documentation** ([docs.abp.io](https://docs.abp.io)) and **Angular official guides**. +- Prioritize **readability, maintainability, and performance**. +- Write **idiomatic** and **self-documenting** code. --- -## 🧪 Testing Strategies - -### Component Testing - -```typescript -describe('BookListComponent', () => { - let component: BookListComponent; - let fixture: ComponentFixture; - let mockBookService: jasmine.SpyObj; - - beforeEach(async () => { - mockBookService = jasmine.createSpyObj('BookService', [ - 'getList', - 'create', - 'delete' - ]); - - await TestBed.configureTestingModule({ - imports: [BookListComponent], - providers: [ - { provide: BookService, useValue: mockBookService } - ] - }).compileComponents(); - - fixture = TestBed.createComponent(BookListComponent); - component = fixture.componentInstance; - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - it('should load books on init', () => { - const mockBooks = [ - { id: '1', name: 'Book 1', price: 10 }, - { id: '2', name: 'Book 2', price: 20 } - ]; - - mockBookService.getList.and.returnValue(of({ - items: mockBooks, - totalCount: 2 - })); - - component.ngOnInit(); - - expect(mockBookService.getList).toHaveBeenCalled(); - }); -}); -``` +## ⚙️ 2. ABP / .NET Development Rules + +### Code Style and Structure +- Follow ABP’s standard folder structure: + - `*.Application`, `*.Domain`, `*.EntityFrameworkCore`, `*.HttpApi` +- Write concise, idiomatic C# code using modern language features. +- Apply **modular and layered design** (Domain, Application, Infrastructure, UI). +- Prefer **LINQ** and **lambda expressions** for collection operations. +- Use **descriptive method and variable names** (`GetActiveUsers`, `CalculateTotalAmount`). + +### Naming Conventions +- **PascalCase** → Classes, Methods, Properties +- **camelCase** → Local variables and private fields +- **UPPER_CASE** → Constants +- Prefix interfaces with **`I`** (e.g., `IUserRepository`). + +### C# and .NET Usage +- Use **C# 10+ features** (records, pattern matching, null-coalescing assignment). +- Utilize **ABP modules** (Permission Management, Setting Management, Audit Logging). +- Integrate **Entity Framework Core** with ABP’s repository abstractions. + +### Syntax and Formatting +- Follow [Microsoft C# Coding Conventions](https://learn.microsoft.com/dotnet/csharp/fundamentals/coding-style/coding-conventions). +- Use `var` when the type is clear. +- Use `string interpolation` and null-conditional operators. +- Keep code consistent and well-formatted. + +### Error Handling and Validation +- Use exceptions only for exceptional cases. +- Log errors via ABP’s built-in logging or a compatible provider. +- Validate models with **DataAnnotations** or **FluentValidation**. +- Rely on ABP’s global exception middleware for unified responses. +- Return consistent HTTP status codes and error DTOs. + +### API Design +- Build RESTful APIs via `HttpApi` layer and **ABP conventional controllers**. +- Use **attribute-based routing** and versioning when needed. +- Apply **action filters/middleware** for cross-cutting concerns (auditing, authorization). + +### Performance Optimization +- Use `async/await` for I/O operations. +- Use `IDistributedCache` over `IMemoryCache`. +- Avoid N+1 queries — include relations explicitly. +- Implement pagination with `PagedResultDto`. + +### Key Conventions +- Use **Dependency Injection** via ABP’s DI system. +- Apply **repository pattern** or EF Core directly as needed. +- Use **AutoMapper** or ABP object mapping for DTOs. +- Implement **background jobs** with ABP’s job system or `IHostedService`. +- Follow **domain-driven design (DDD)** principles: + - Business rules in Domain layer. + - Use `AuditedAggregateRoot`, `FullAuditedEntity`, etc. +- Avoid unnecessary dependencies between layers. + +### Testing +- Use **xUnit**, **Shouldly**, and **NSubstitute** for testing. +- Write **unit and integration tests** per module (`Application.Tests`, `Domain.Tests`). +- Mock dependencies properly and use ABP’s test base classes. + +### Security +- Use **OpenIddict** for authentication & authorization. +- Implement permission checks through ABP’s infrastructure. +- Enforce **HTTPS** and properly configure **CORS**. + +### API Documentation +- Use **Swagger / OpenAPI** (Swashbuckle or NSwag). +- Add XML comments to controllers and DTOs. +- Follow ABP’s documentation conventions for module APIs. + +**Reference Best Practices:** +- [Domain Services](https://abp.io/docs/latest/framework/architecture/best-practices/domain-services) +- [Repositories](https://abp.io/docs/latest/framework/architecture/best-practices/repositories) +- [Entities](https://abp.io/docs/latest/framework/architecture/best-practices/entities) +- [Application Services](https://abp.io/docs/latest/framework/architecture/best-practices/application-services) +- [DTOs](https://abp.io/docs/latest/framework/architecture/best-practices/data-transfer-objects) +- [Entity Framework Integration](https://abp.io/docs/latest/framework/architecture/best-practices/entity-framework-core-integration) --- -## 🚀 Performance Optimization - -### 1. Change Detection Strategy -```typescript -@Component({ - changeDetection: ChangeDetectionStrategy.OnPush -}) -``` - -### 2. TrackBy Functions -```typescript -trackByBookId(index: number, book: BookDto): string { - return book.id; -} -``` - -### 3. Lazy Loading -```typescript -const routes: Routes = [ - { - path: 'books', - loadChildren: () => import('./books/books.routes') - .then(m => m.BOOKS_ROUTES) - } -]; -``` - -### 4. Virtual Scrolling (for large lists) -```typescript - -
- {{ book.name }} -
-
-``` - ---- - -## 📁 File Structure (Nx Workspace) - -``` -libs/ - books/ - feature/ - src/ - lib/ - components/ - book-list/ - book-form/ - book-detail/ - services/ - state/ - guards/ - books-feature.routes.ts - index.ts - data-access/ - src/ - lib/ - services/ - models/ - index.ts - ui/ - src/ - lib/ - components/ - index.ts -``` - ---- - -## ✅ Quality Checklist - -Before committing code, verify: -- [ ] TypeScript strict mode compliance -- [ ] No `any` types -- [ ] OnPush change detection -- [ ] Proper unsubscription -- [ ] Error handling implemented -- [ ] Unit tests written -- [ ] Localization keys used -- [ ] Permission checks added -- [ ] Accessibility attributes included -- [ ] Performance optimized (trackBy, lazy loading) -- [ ] Security validated -- [ ] Code formatted (Prettier) -- [ ] Linting passed +## 🌐 3. Angular / TypeScript Development Rules + +### TypeScript Best Practices +- Enable **strict type checking** in `tsconfig.json`. +- Use **type inference** when the type is obvious. +- Avoid `any`; use `unknown` or generics instead. +- Use interfaces and types for clarity and structure. + +### Angular Best Practices +- Prefer **standalone components** (no `NgModules`). +- Do **NOT** set `standalone: true` manually — it’s default. +- Use **signals** for state management. +- Implement **lazy loading** for feature routes. +- Avoid `@HostBinding` / `@HostListener`; use `host` object in decorators. +- Use **`NgOptimizedImage`** for static images (not base64). + +### Components +- Keep components small, focused, and reusable. +- Use `input()` and `output()` functions instead of decorators. +- Use `computed()` for derived state. +- Always set `changeDetection: ChangeDetectionStrategy.OnPush`. +- Use **inline templates** for small components. +- Prefer **Reactive Forms** over template-driven forms. +- Avoid `ngClass` → use `[class]` bindings. +- Avoid `ngStyle` → use `[style]` bindings. + +### State Management +- Manage **local component state** with signals. +- Use **`computed()`** for derived data. +- Keep state transformations **pure and predictable**. +- Avoid `mutate()` on signals — use `update()` or `set()`. + +### Templates +- Use **native control flow** (`@if`, `@for`, `@switch`) instead of structural directives. +- Keep templates minimal and declarative. +- Use the **async pipe** for observable bindings. + +### Services +- Design services for **single responsibility**. +- Provide services using `providedIn: 'root'`. +- Use the **`inject()` function** instead of constructor injection. --- -## 🚫 Common Anti-Patterns - -| Anti-Pattern | Why It's Bad | Better Approach | -|--------------|--------------|-----------------| -| Using `any` | Loses type safety | Use proper types or `unknown` | -| No unsubscribe | Memory leaks | Use takeUntil or async pipe | -| Logic in templates | Hard to test | Move to component/service | -| Nested subscriptions | Hard to maintain | Use RxJS operators | -| Direct state mutation | Breaks change detection | Use immutable patterns | -| Missing error handling | Poor UX | Always handle errors | -| Hardcoded strings | Not localizable | Use localization system | - ---- - -## 📚 Resources - -- [Angular Style Guide](https://angular.io/guide/styleguide) -- [ABP Framework Documentation](https://docs.abp.io) -- [RxJS Documentation](https://rxjs.dev) -- [Nx Documentation](https://nx.dev) -- [NGXS Documentation](https://www.ngxs.io) +## 🔒 4. Combined Full-Stack Practices +- Ensure backend and frontend follow consistent **DTO contracts** and **naming conventions**. +- Maintain shared models (e.g., via a `contracts` package or OpenAPI generation). +- Version APIs carefully and handle changes in Angular clients. +- Use ABP’s **CORS**, **Swagger**, and **Identity** modules to simplify frontend integration. +- Apply **global error handling** and consistent response wrappers in both layers. +- Monitor performance with tools like **Application Insights**, **ABP auditing**, or **Angular profiler**. --- -Follow these guidelines to build maintainable, performant, and secure Angular applications with ABP Framework. +## ✅ Summary +This document defines a unified standard for developing **ABP + Angular full-stack applications**, ensuring: +- Code is **modular**, **performant**, and **maintainable**. +- Teams follow **consistent conventions** across backend and frontend. +- Every layer (Domain, Application, UI) is **clean, testable, and scalable**. From 73a8beba67761c9fad6cef5e72505af7a1000a74 Mon Sep 17 00:00:00 2001 From: Fahri Gedik Date: Fri, 10 Oct 2025 09:23:24 +0300 Subject: [PATCH 03/27] 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. --- .../src/commands/ai-config/index.ts | 51 ++++++++++--------- .../src/commands/ai-config/model.ts | 2 +- .../src/commands/ai-config/schema.json | 22 ++------ 3 files changed, 33 insertions(+), 42 deletions(-) diff --git a/npm/ng-packs/packages/schematics/src/commands/ai-config/index.ts b/npm/ng-packs/packages/schematics/src/commands/ai-config/index.ts index 10691f6609..5b510af111 100644 --- a/npm/ng-packs/packages/schematics/src/commands/ai-config/index.ts +++ b/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 = { claude: '.claude/CLAUDE.md', diff --git a/npm/ng-packs/packages/schematics/src/commands/ai-config/model.ts b/npm/ng-packs/packages/schematics/src/commands/ai-config/model.ts index 8a10f85509..ab871971c2 100644 --- a/npm/ng-packs/packages/schematics/src/commands/ai-config/model.ts +++ b/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; } diff --git a/npm/ng-packs/packages/schematics/src/commands/ai-config/schema.json b/npm/ng-packs/packages/schematics/src/commands/ai-config/schema.json index 7ad18a1fe4..0d5e0117ed 100644 --- a/npm/ng-packs/packages/schematics/src/commands/ai-config/schema.json +++ b/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": { From 8c75745f87b6d5c0a51a2a55445c989e18ddd259 Mon Sep 17 00:00:00 2001 From: Fahri Gedik Date: Fri, 10 Oct 2025 12:12:08 +0300 Subject: [PATCH 04/27] Add ABP Angular component replacement documentation Expanded guidelines in multiple AI config instruction files to include details and examples for using ABP Angular's ReplaceableComponentsService. This addition covers key features, usage, important notes, and links to full documentation for customizing and replacing default components, layouts, and UI elements. --- .../ai-config/files/claude/.claude/CLAUDE.md | 30 +++++++++++++++++++ .../copilot/.github/copilot-instructions.md | 30 +++++++++++++++++++ .../files/cursor/.cursor/rules/cursor.mdc | 30 +++++++++++++++++++ .../ai-config/files/gemini/.gemini/GEMINI.md | 30 +++++++++++++++++++ .../files/junie/.junie/guidelines.md | 30 +++++++++++++++++++ .../windsurf/.windsurf/rules/guidelines.md | 30 +++++++++++++++++++ 6 files changed, 180 insertions(+) diff --git a/npm/ng-packs/packages/schematics/src/commands/ai-config/files/claude/.claude/CLAUDE.md b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/claude/.claude/CLAUDE.md index a150753322..32dfab0275 100644 --- a/npm/ng-packs/packages/schematics/src/commands/ai-config/files/claude/.claude/CLAUDE.md +++ b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/claude/.claude/CLAUDE.md @@ -137,6 +137,36 @@ You write **clean, maintainable, and modular** code following **ABP, ASP.NET Cor - Provide services using `providedIn: 'root'`. - Use the **`inject()` function** instead of constructor injection. +### Component Replacement +ABP Angular provides a powerful **component replacement** system via `ReplaceableComponentsService`: + +**Key Features:** +- Replace ABP default components (Roles, Users, Tenants, etc.) with custom implementations +- Replace layouts (Application, Account, Empty) +- Replace UI elements (Logo, Routes, NavItems) + +**Basic Usage:** +```typescript +import { ReplaceableComponentsService } from '@abp/ng.core'; +import { eIdentityComponents } from '@abp/ng.identity'; + +constructor(private replaceableComponents: ReplaceableComponentsService) { + this.replaceableComponents.add({ + component: YourCustomComponent, + key: eIdentityComponents.Roles, + }); +} +``` + +**Important Notes:** +- Component templates must include `` for layouts +- Use the second parameter as `true` for runtime replacement (refreshes route) +- Runtime replacement clears component state and re-runs initialization logic + +**📚 Full Documentation:** +For detailed examples, layout replacement, and advanced scenarios: +[Component Replacement Guide](https://abp.io/docs/latest/framework/ui/angular/customization-user-interface) + --- ## 🔒 4. Combined Full-Stack Practices diff --git a/npm/ng-packs/packages/schematics/src/commands/ai-config/files/copilot/.github/copilot-instructions.md b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/copilot/.github/copilot-instructions.md index a150753322..32dfab0275 100644 --- a/npm/ng-packs/packages/schematics/src/commands/ai-config/files/copilot/.github/copilot-instructions.md +++ b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/copilot/.github/copilot-instructions.md @@ -137,6 +137,36 @@ You write **clean, maintainable, and modular** code following **ABP, ASP.NET Cor - Provide services using `providedIn: 'root'`. - Use the **`inject()` function** instead of constructor injection. +### Component Replacement +ABP Angular provides a powerful **component replacement** system via `ReplaceableComponentsService`: + +**Key Features:** +- Replace ABP default components (Roles, Users, Tenants, etc.) with custom implementations +- Replace layouts (Application, Account, Empty) +- Replace UI elements (Logo, Routes, NavItems) + +**Basic Usage:** +```typescript +import { ReplaceableComponentsService } from '@abp/ng.core'; +import { eIdentityComponents } from '@abp/ng.identity'; + +constructor(private replaceableComponents: ReplaceableComponentsService) { + this.replaceableComponents.add({ + component: YourCustomComponent, + key: eIdentityComponents.Roles, + }); +} +``` + +**Important Notes:** +- Component templates must include `` for layouts +- Use the second parameter as `true` for runtime replacement (refreshes route) +- Runtime replacement clears component state and re-runs initialization logic + +**📚 Full Documentation:** +For detailed examples, layout replacement, and advanced scenarios: +[Component Replacement Guide](https://abp.io/docs/latest/framework/ui/angular/customization-user-interface) + --- ## 🔒 4. Combined Full-Stack Practices diff --git a/npm/ng-packs/packages/schematics/src/commands/ai-config/files/cursor/.cursor/rules/cursor.mdc b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/cursor/.cursor/rules/cursor.mdc index a150753322..32dfab0275 100644 --- a/npm/ng-packs/packages/schematics/src/commands/ai-config/files/cursor/.cursor/rules/cursor.mdc +++ b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/cursor/.cursor/rules/cursor.mdc @@ -137,6 +137,36 @@ You write **clean, maintainable, and modular** code following **ABP, ASP.NET Cor - Provide services using `providedIn: 'root'`. - Use the **`inject()` function** instead of constructor injection. +### Component Replacement +ABP Angular provides a powerful **component replacement** system via `ReplaceableComponentsService`: + +**Key Features:** +- Replace ABP default components (Roles, Users, Tenants, etc.) with custom implementations +- Replace layouts (Application, Account, Empty) +- Replace UI elements (Logo, Routes, NavItems) + +**Basic Usage:** +```typescript +import { ReplaceableComponentsService } from '@abp/ng.core'; +import { eIdentityComponents } from '@abp/ng.identity'; + +constructor(private replaceableComponents: ReplaceableComponentsService) { + this.replaceableComponents.add({ + component: YourCustomComponent, + key: eIdentityComponents.Roles, + }); +} +``` + +**Important Notes:** +- Component templates must include `` for layouts +- Use the second parameter as `true` for runtime replacement (refreshes route) +- Runtime replacement clears component state and re-runs initialization logic + +**📚 Full Documentation:** +For detailed examples, layout replacement, and advanced scenarios: +[Component Replacement Guide](https://abp.io/docs/latest/framework/ui/angular/customization-user-interface) + --- ## 🔒 4. Combined Full-Stack Practices diff --git a/npm/ng-packs/packages/schematics/src/commands/ai-config/files/gemini/.gemini/GEMINI.md b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/gemini/.gemini/GEMINI.md index a150753322..32dfab0275 100644 --- a/npm/ng-packs/packages/schematics/src/commands/ai-config/files/gemini/.gemini/GEMINI.md +++ b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/gemini/.gemini/GEMINI.md @@ -137,6 +137,36 @@ You write **clean, maintainable, and modular** code following **ABP, ASP.NET Cor - Provide services using `providedIn: 'root'`. - Use the **`inject()` function** instead of constructor injection. +### Component Replacement +ABP Angular provides a powerful **component replacement** system via `ReplaceableComponentsService`: + +**Key Features:** +- Replace ABP default components (Roles, Users, Tenants, etc.) with custom implementations +- Replace layouts (Application, Account, Empty) +- Replace UI elements (Logo, Routes, NavItems) + +**Basic Usage:** +```typescript +import { ReplaceableComponentsService } from '@abp/ng.core'; +import { eIdentityComponents } from '@abp/ng.identity'; + +constructor(private replaceableComponents: ReplaceableComponentsService) { + this.replaceableComponents.add({ + component: YourCustomComponent, + key: eIdentityComponents.Roles, + }); +} +``` + +**Important Notes:** +- Component templates must include `` for layouts +- Use the second parameter as `true` for runtime replacement (refreshes route) +- Runtime replacement clears component state and re-runs initialization logic + +**📚 Full Documentation:** +For detailed examples, layout replacement, and advanced scenarios: +[Component Replacement Guide](https://abp.io/docs/latest/framework/ui/angular/customization-user-interface) + --- ## 🔒 4. Combined Full-Stack Practices diff --git a/npm/ng-packs/packages/schematics/src/commands/ai-config/files/junie/.junie/guidelines.md b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/junie/.junie/guidelines.md index a150753322..32dfab0275 100644 --- a/npm/ng-packs/packages/schematics/src/commands/ai-config/files/junie/.junie/guidelines.md +++ b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/junie/.junie/guidelines.md @@ -137,6 +137,36 @@ You write **clean, maintainable, and modular** code following **ABP, ASP.NET Cor - Provide services using `providedIn: 'root'`. - Use the **`inject()` function** instead of constructor injection. +### Component Replacement +ABP Angular provides a powerful **component replacement** system via `ReplaceableComponentsService`: + +**Key Features:** +- Replace ABP default components (Roles, Users, Tenants, etc.) with custom implementations +- Replace layouts (Application, Account, Empty) +- Replace UI elements (Logo, Routes, NavItems) + +**Basic Usage:** +```typescript +import { ReplaceableComponentsService } from '@abp/ng.core'; +import { eIdentityComponents } from '@abp/ng.identity'; + +constructor(private replaceableComponents: ReplaceableComponentsService) { + this.replaceableComponents.add({ + component: YourCustomComponent, + key: eIdentityComponents.Roles, + }); +} +``` + +**Important Notes:** +- Component templates must include `` for layouts +- Use the second parameter as `true` for runtime replacement (refreshes route) +- Runtime replacement clears component state and re-runs initialization logic + +**📚 Full Documentation:** +For detailed examples, layout replacement, and advanced scenarios: +[Component Replacement Guide](https://abp.io/docs/latest/framework/ui/angular/customization-user-interface) + --- ## 🔒 4. Combined Full-Stack Practices diff --git a/npm/ng-packs/packages/schematics/src/commands/ai-config/files/windsurf/.windsurf/rules/guidelines.md b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/windsurf/.windsurf/rules/guidelines.md index a150753322..32dfab0275 100644 --- a/npm/ng-packs/packages/schematics/src/commands/ai-config/files/windsurf/.windsurf/rules/guidelines.md +++ b/npm/ng-packs/packages/schematics/src/commands/ai-config/files/windsurf/.windsurf/rules/guidelines.md @@ -137,6 +137,36 @@ You write **clean, maintainable, and modular** code following **ABP, ASP.NET Cor - Provide services using `providedIn: 'root'`. - Use the **`inject()` function** instead of constructor injection. +### Component Replacement +ABP Angular provides a powerful **component replacement** system via `ReplaceableComponentsService`: + +**Key Features:** +- Replace ABP default components (Roles, Users, Tenants, etc.) with custom implementations +- Replace layouts (Application, Account, Empty) +- Replace UI elements (Logo, Routes, NavItems) + +**Basic Usage:** +```typescript +import { ReplaceableComponentsService } from '@abp/ng.core'; +import { eIdentityComponents } from '@abp/ng.identity'; + +constructor(private replaceableComponents: ReplaceableComponentsService) { + this.replaceableComponents.add({ + component: YourCustomComponent, + key: eIdentityComponents.Roles, + }); +} +``` + +**Important Notes:** +- Component templates must include `` for layouts +- Use the second parameter as `true` for runtime replacement (refreshes route) +- Runtime replacement clears component state and re-runs initialization logic + +**📚 Full Documentation:** +For detailed examples, layout replacement, and advanced scenarios: +[Component Replacement Guide](https://abp.io/docs/latest/framework/ui/angular/customization-user-interface) + --- ## 🔒 4. Combined Full-Stack Practices From cecec7505fe5e92efbb2415c4b3774c85bfd0285 Mon Sep 17 00:00:00 2001 From: Fahri Gedik Date: Thu, 30 Oct 2025 14:36:29 +0300 Subject: [PATCH 05/27] Trim targetProject input before lookup Ensures that leading and trailing whitespace is removed from the targetProject option before attempting to retrieve the project from the workspace. This prevents errors when users accidentally include extra spaces in the project name. --- .../packages/schematics/src/commands/ai-config/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/npm/ng-packs/packages/schematics/src/commands/ai-config/index.ts b/npm/ng-packs/packages/schematics/src/commands/ai-config/index.ts index 5b510af111..9f1606c885 100644 --- a/npm/ng-packs/packages/schematics/src/commands/ai-config/index.ts +++ b/npm/ng-packs/packages/schematics/src/commands/ai-config/index.ts @@ -38,10 +38,11 @@ export default function (options: AiConfigSchema): Rule { let targetPath = '/'; if (options.targetProject) { - const project = workspace.projects.get(options.targetProject); + const trimmedTargetProject = options.targetProject.trim(); + const project = workspace.projects.get(trimmedTargetProject); if (!project) { throw new SchematicsException( - `Project "${options.targetProject}" not found in workspace.` + `Project "${trimmedTargetProject}" not found in workspace.` ); } targetPath = normalize(project.root); From 12fa8bcae98a1ebd019462ae309a28278f4cd83d Mon Sep 17 00:00:00 2001 From: Fahri Gedik Date: Thu, 20 Nov 2025 16:22:17 +0300 Subject: [PATCH 06/27] Simplify tool string splitting in ai-config command Refactored the splitting of the 'tool' option to use a single regex for whitespace and commas, removing the need for an explicit trim(). This streamlines parsing and improves code clarity. --- .../packages/schematics/src/commands/ai-config/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/npm/ng-packs/packages/schematics/src/commands/ai-config/index.ts b/npm/ng-packs/packages/schematics/src/commands/ai-config/index.ts index 9f1606c885..1256927a86 100644 --- a/npm/ng-packs/packages/schematics/src/commands/ai-config/index.ts +++ b/npm/ng-packs/packages/schematics/src/commands/ai-config/index.ts @@ -17,8 +17,7 @@ export default function (options: AiConfigSchema): Rule { } const tools = options.tool - .split(/[,\s]+/) - .map(t => t.trim()) + .split(/[\s,]+/) .filter(t => t) as AiTool[]; const validTools: AiTool[] = ['claude', 'copilot', 'cursor', 'gemini', 'junie', 'windsurf']; From fef11078680d4f509153f29ea2af616df0658b3a Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 22 Nov 2025 15:26:49 +0800 Subject: [PATCH 07/27] Upgrade `Swashbuckle.AspNetCore` to v10.0. --- Directory.Packages.props | 2 +- ...bpSwaggerGenServiceCollectionExtensions.cs | 39 +++++-------------- .../AbpSwashbuckleDocumentFilter.cs | 20 ++++++++-- .../AbpSwashbuckleEnumSchemaFilter.cs | 18 ++++----- .../BloggingTestAppModule.cs | 2 +- .../CmsKitHttpApiHostModule.cs | 2 +- .../CmsKitIdentityServerModule.cs | 2 +- .../CmsKitWebHostModule.cs | 2 +- .../CmsKitWebUnifiedModule.cs | 2 +- .../app/VoloDocs.Web/VoloDocsWebModule.cs | 2 +- .../app/OpenIddict.Demo.API/Program.cs | 2 +- .../DemoAppModule.cs | 2 +- .../MyProjectNameModule.cs | 2 +- .../MyProjectNameModule.cs | 2 +- .../Server.Mongo/MyProjectNameHostModule.cs | 2 +- .../Server/MyProjectNameHostModule.cs | 2 +- .../MyProjectNameModule.cs | 2 +- .../MyProjectNameModule.cs | 2 +- .../MyProjectNameModule.cs | 2 +- .../MyProjectNameModule.cs | 2 +- .../MyProjectNameBlazorModule.cs | 2 +- .../MyProjectNameBlazorModule.cs | 2 +- .../MyProjectNameBlazorModule.cs | 2 +- .../MyProjectNameBlazorModule.cs | 2 +- .../MyProjectNameHttpApiHostModule.cs | 2 +- .../MyProjectNameHttpApiHostModule.cs | 2 +- .../MyProjectNameWebModule.cs | 2 +- .../MyProjectNameWebModule.cs | 2 +- .../MyProjectNameAuthServerModule.cs | 2 +- .../MyProjectNameBlazorHostModule.cs | 2 +- .../MyProjectNameHttpApiHostModule.cs | 2 +- .../MyProjectNameWebHostModule.cs | 2 +- .../MyProjectNameWebUnifiedModule.cs | 2 +- 33 files changed, 66 insertions(+), 71 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 350d488d82..8937f7b8ef 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -168,7 +168,7 @@ - + diff --git a/framework/src/Volo.Abp.Swashbuckle/Microsoft/Extensions/DependencyInjection/AbpSwaggerGenServiceCollectionExtensions.cs b/framework/src/Volo.Abp.Swashbuckle/Microsoft/Extensions/DependencyInjection/AbpSwaggerGenServiceCollectionExtensions.cs index 1238d766be..d63ca833eb 100644 --- a/framework/src/Volo.Abp.Swashbuckle/Microsoft/Extensions/DependencyInjection/AbpSwaggerGenServiceCollectionExtensions.cs +++ b/framework/src/Volo.Abp.Swashbuckle/Microsoft/Extensions/DependencyInjection/AbpSwaggerGenServiceCollectionExtensions.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using JetBrains.Annotations; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using Swashbuckle.AspNetCore.SwaggerGen; using Swashbuckle.AspNetCore.SwaggerUI; using Volo.Abp.Content; @@ -21,7 +21,7 @@ public static class AbpSwaggerGenServiceCollectionExtensions { Func remoteStreamContentSchemaFactory = () => new OpenApiSchema() { - Type = "string", + Type = JsonSchemaType.String, Format = "binary" }; @@ -42,7 +42,7 @@ public static class AbpSwaggerGenServiceCollectionExtensions { var authorizationUrl = new Uri($"{authority.TrimEnd('/')}{authorizationEndpoint.EnsureStartsWith('/')}"); var tokenUrl = new Uri($"{authority.TrimEnd('/')}{tokenEndpoint.EnsureStartsWith('/')}"); - + return services .AddAbpSwaggerGen() .AddSwaggerGen( @@ -62,19 +62,9 @@ public static class AbpSwaggerGenServiceCollectionExtensions } }); - options.AddSecurityRequirement(new OpenApiSecurityRequirement + options.AddSecurityRequirement(document => new OpenApiSecurityRequirement() { - { - new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Type = ReferenceType.SecurityScheme, - Id = "oauth2" - } - }, - Array.Empty() - } + [new OpenApiSecuritySchemeReference("oauth2", document)] = [] }); setupAction?.Invoke(options); @@ -100,7 +90,7 @@ public static class AbpSwaggerGenServiceCollectionExtensions swaggerUiOptions.ConfigObject.AdditionalItems["oidcSupportedScopes"] = scopes; swaggerUiOptions.ConfigObject.AdditionalItems["oidcDiscoveryEndpoint"] = discoveryUrl; }); - + return services .AddAbpSwaggerGen() .AddSwaggerGen( @@ -112,24 +102,15 @@ public static class AbpSwaggerGenServiceCollectionExtensions OpenIdConnectUrl = new Uri(RemoveTenantPlaceholders(discoveryUrl)) }); - options.AddSecurityRequirement(new OpenApiSecurityRequirement + options.AddSecurityRequirement(document => new OpenApiSecurityRequirement() { - { - new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Type = ReferenceType.SecurityScheme, - Id = "oidc" - } - }, - Array.Empty() - } + [new OpenApiSecuritySchemeReference("oauth2", document)] = [] }); + setupAction?.Invoke(options); }); } - + private static string RemoveTenantPlaceholders(string url) { return url diff --git a/framework/src/Volo.Abp.Swashbuckle/Volo/Abp/Swashbuckle/AbpSwashbuckleDocumentFilter.cs b/framework/src/Volo.Abp.Swashbuckle/Volo/Abp/Swashbuckle/AbpSwashbuckleDocumentFilter.cs index b6dc661410..8c4f06d786 100644 --- a/framework/src/Volo.Abp.Swashbuckle/Volo/Abp/Swashbuckle/AbpSwashbuckleDocumentFilter.cs +++ b/framework/src/Volo.Abp.Swashbuckle/Volo/Abp/Swashbuckle/AbpSwashbuckleDocumentFilter.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using Microsoft.AspNetCore.Mvc.Abstractions; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using Swashbuckle.AspNetCore.SwaggerGen; namespace Volo.Abp.Swashbuckle; @@ -13,7 +13,7 @@ public class AbpSwashbuckleDocumentFilter : IDocumentFilter protected virtual string[] ActionUrlPrefixes { get; set; } = new[] { "Volo." }; protected virtual string RegexConstraintPattern => @":regex\(([^()]*)\)"; - + public virtual void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) { var actionUrls = context.ApiDescriptions @@ -28,6 +28,20 @@ public class AbpSwashbuckleDocumentFilter : IDocumentFilter swaggerDoc .Paths .RemoveAll(path => !actionUrls.Contains(path.Key)); + + var tags = new List(); + foreach (var path in swaggerDoc.Paths) + { + if (path.Value.Operations != null) + { + tags.AddRange(path.Value.Operations.SelectMany(x => + { + return x.Value.Tags?.Select(t => t.Name ?? string.Empty) ?? Array.Empty(); + })); + } + } + tags = tags.Distinct().ToList(); + swaggerDoc.Tags?.RemoveAll(tag => tag.Name == null || !tags.Contains(tag.Name)); } protected virtual string? RemoveRouteParameterConstraints(ActionDescriptor actionDescriptor) @@ -49,7 +63,7 @@ public class AbpSwashbuckleDocumentFilter : IDocumentFilter { break; } - + route = route.Remove(startIndex, (endIndex - startIndex)); } diff --git a/framework/src/Volo.Abp.Swashbuckle/Volo/Abp/Swashbuckle/AbpSwashbuckleEnumSchemaFilter.cs b/framework/src/Volo.Abp.Swashbuckle/Volo/Abp/Swashbuckle/AbpSwashbuckleEnumSchemaFilter.cs index b6a20d2ad2..afd6a141dc 100644 --- a/framework/src/Volo.Abp.Swashbuckle/Volo/Abp/Swashbuckle/AbpSwashbuckleEnumSchemaFilter.cs +++ b/framework/src/Volo.Abp.Swashbuckle/Volo/Abp/Swashbuckle/AbpSwashbuckleEnumSchemaFilter.cs @@ -1,22 +1,22 @@ -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Models; +using System; +using System.Text.Json.Nodes; +using Microsoft.OpenApi; using Swashbuckle.AspNetCore.SwaggerGen; -using System; namespace Volo.Abp.Swashbuckle; public class AbpSwashbuckleEnumSchemaFilter : ISchemaFilter { - public void Apply(OpenApiSchema schema, SchemaFilterContext context) + public void Apply(IOpenApiSchema schema, SchemaFilterContext context) { - if (context.Type.IsEnum) + if (schema is OpenApiSchema openApiScheme && context.Type.IsEnum) { - schema.Enum.Clear(); - schema.Type = "string"; - schema.Format = null; + openApiScheme.Enum?.Clear(); + openApiScheme.Type = JsonSchemaType.String; + openApiScheme.Format = null; foreach (var name in Enum.GetNames(context.Type)) { - schema.Enum.Add(new OpenApiString($"{name}")); + openApiScheme.Enum?.Add(JsonNode.Parse($"\"{name}\"")!); } } } diff --git a/modules/blogging/app/Volo.BloggingTestApp/BloggingTestAppModule.cs b/modules/blogging/app/Volo.BloggingTestApp/BloggingTestAppModule.cs index 47447e1fdf..c239da4234 100644 --- a/modules/blogging/app/Volo.BloggingTestApp/BloggingTestAppModule.cs +++ b/modules/blogging/app/Volo.BloggingTestApp/BloggingTestAppModule.cs @@ -11,7 +11,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using Swashbuckle.AspNetCore.Swagger; using Volo.Abp; using Volo.Abp.Account; diff --git a/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/CmsKitHttpApiHostModule.cs b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/CmsKitHttpApiHostModule.cs index 1f451f58d9..bfc910c7c6 100644 --- a/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/CmsKitHttpApiHostModule.cs +++ b/modules/cms-kit/host/Volo.CmsKit.HttpApi.Host/CmsKitHttpApiHostModule.cs @@ -13,7 +13,7 @@ using Microsoft.Extensions.Hosting; using Volo.CmsKit.EntityFrameworkCore; using Volo.CmsKit.MultiTenancy; using StackExchange.Redis; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using Swashbuckle.AspNetCore.Swagger; using Volo.Abp; using Volo.Abp.AspNetCore.MultiTenancy; diff --git a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/CmsKitIdentityServerModule.cs b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/CmsKitIdentityServerModule.cs index 623b417bc7..933dc8f50b 100644 --- a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/CmsKitIdentityServerModule.cs +++ b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/CmsKitIdentityServerModule.cs @@ -8,7 +8,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Volo.CmsKit.MultiTenancy; using StackExchange.Redis; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using Swashbuckle.AspNetCore.Swagger; using Volo.Abp; using Volo.Abp.Account; diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Host/CmsKitWebHostModule.cs b/modules/cms-kit/host/Volo.CmsKit.Web.Host/CmsKitWebHostModule.cs index 522771c77b..7054a6f1fe 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Host/CmsKitWebHostModule.cs +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Host/CmsKitWebHostModule.cs @@ -2,7 +2,7 @@ using System; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using System.IO; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.DataProtection; diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/CmsKitWebUnifiedModule.cs b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/CmsKitWebUnifiedModule.cs index c216a6ba38..e8169fbfef 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/CmsKitWebUnifiedModule.cs +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/CmsKitWebUnifiedModule.cs @@ -3,7 +3,7 @@ using System.IO; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using Volo.Abp; using Volo.Abp.Account; using Volo.Abp.Account.Web; diff --git a/modules/docs/app/VoloDocs.Web/VoloDocsWebModule.cs b/modules/docs/app/VoloDocs.Web/VoloDocsWebModule.cs index 3d49767cdb..4a7f511bff 100644 --- a/modules/docs/app/VoloDocs.Web/VoloDocsWebModule.cs +++ b/modules/docs/app/VoloDocs.Web/VoloDocsWebModule.cs @@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.DependencyInjection; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using Swashbuckle.AspNetCore.Swagger; using Volo.Abp; using Volo.Abp.Account.Web; diff --git a/modules/openiddict/app/OpenIddict.Demo.API/Program.cs b/modules/openiddict/app/OpenIddict.Demo.API/Program.cs index 0938f87e6c..2c4d359d73 100644 --- a/modules/openiddict/app/OpenIddict.Demo.API/Program.cs +++ b/modules/openiddict/app/OpenIddict.Demo.API/Program.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Authentication.JwtBearer; using OpenIddict.Demo.API; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using Swashbuckle.AspNetCore.SwaggerGen; using Swashbuckle.AspNetCore.SwaggerUI; diff --git a/modules/setting-management/app/Volo.Abp.SettingManagement.DemoApp/DemoAppModule.cs b/modules/setting-management/app/Volo.Abp.SettingManagement.DemoApp/DemoAppModule.cs index cbb97606bd..4671a82ad6 100644 --- a/modules/setting-management/app/Volo.Abp.SettingManagement.DemoApp/DemoAppModule.cs +++ b/modules/setting-management/app/Volo.Abp.SettingManagement.DemoApp/DemoAppModule.cs @@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using Volo.Abp.Account; using Volo.Abp.Account.Web; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic; diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyProjectNameModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyProjectNameModule.cs index 86c8803963..151f57de6c 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyProjectNameModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/MyProjectNameModule.cs @@ -3,7 +3,7 @@ using Blazorise.Icons.FontAwesome; using Microsoft.AspNetCore.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using MyCompanyName.MyProjectName.Blazor.Server.Mongo.Components; using MyCompanyName.MyProjectName.Data; using MyCompanyName.MyProjectName.Localization; diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameModule.cs index 762ca3b7b1..8bf5f371fe 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameModule.cs @@ -3,7 +3,7 @@ using Blazorise.Icons.FontAwesome; using Microsoft.AspNetCore.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using MyCompanyName.MyProjectName.Blazor.Server.Components; using MyCompanyName.MyProjectName.Data; using MyCompanyName.MyProjectName.Localization; diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/MyProjectNameHostModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/MyProjectNameHostModule.cs index 42ad99abd1..4985e565ba 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/MyProjectNameHostModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/MyProjectNameHostModule.cs @@ -2,7 +2,7 @@ using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using MyCompanyName.MyProjectName.Data; using MyCompanyName.MyProjectName.Localization; using MyCompanyName.MyProjectName.Components; diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/MyProjectNameHostModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/MyProjectNameHostModule.cs index f77a4069e1..c4dade54cc 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/MyProjectNameHostModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/MyProjectNameHostModule.cs @@ -2,7 +2,7 @@ using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using MyCompanyName.MyProjectName.Data; using MyCompanyName.MyProjectName.Localization; using MyCompanyName.MyProjectName.Components; diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyProjectNameModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyProjectNameModule.cs index 93a9bd89b6..0da4008f3c 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyProjectNameModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/MyProjectNameModule.cs @@ -2,7 +2,7 @@ using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using MyCompanyName.MyProjectName.Data; using MyCompanyName.MyProjectName.Localization; using OpenIddict.Validation.AspNetCore; diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyProjectNameModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyProjectNameModule.cs index d0bc2944dc..7eaaa5261d 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyProjectNameModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/MyProjectNameModule.cs @@ -2,7 +2,7 @@ using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using MyCompanyName.MyProjectName.Data; using MyCompanyName.MyProjectName.Localization; using OpenIddict.Validation.AspNetCore; diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyProjectNameModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyProjectNameModule.cs index b0331d6144..b8f9a7906a 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyProjectNameModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/MyProjectNameModule.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using MyCompanyName.MyProjectName.Data; using MyCompanyName.MyProjectName.Localization; using MyCompanyName.MyProjectName.Menus; diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/MyProjectNameModule.cs b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/MyProjectNameModule.cs index 4869ab492e..0657ef6803 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/MyProjectNameModule.cs +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/MyProjectNameModule.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using MyCompanyName.MyProjectName.Data; using MyCompanyName.MyProjectName.Localization; using MyCompanyName.MyProjectName.Menus; diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyProjectNameBlazorModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyProjectNameBlazorModule.cs index 4d94c3bde9..325ee6805b 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyProjectNameBlazorModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/MyProjectNameBlazorModule.cs @@ -14,7 +14,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Protocols.OpenIdConnect; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using MyCompanyName.MyProjectName.Blazor.Server.Tiered.Components; using MyCompanyName.MyProjectName.Blazor.Server.Tiered.Menus; using MyCompanyName.MyProjectName.Localization; diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameBlazorModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameBlazorModule.cs index 5d27bb122f..658d6d80ca 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameBlazorModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/MyProjectNameBlazorModule.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using MyCompanyName.MyProjectName.Blazor.Server.Components; using MyCompanyName.MyProjectName.Blazor.Server.Menus; using MyCompanyName.MyProjectName.EntityFrameworkCore; diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/MyProjectNameBlazorModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/MyProjectNameBlazorModule.cs index 0849b09d6d..2a31f2a763 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/MyProjectNameBlazorModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/MyProjectNameBlazorModule.cs @@ -14,7 +14,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Protocols.OpenIdConnect; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client; using MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Client.Menus; using MyCompanyName.MyProjectName.Blazor.WebApp.Tiered.Components; diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/MyProjectNameBlazorModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/MyProjectNameBlazorModule.cs index 6a75e85ae0..3a7b435244 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/MyProjectNameBlazorModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/MyProjectNameBlazorModule.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using MyCompanyName.MyProjectName.Blazor.WebApp.Client; using MyCompanyName.MyProjectName.Blazor.WebApp.Client.Menus; using MyCompanyName.MyProjectName.Blazor.WebApp.Components; diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs index 73033bdda1..f8e42b82c6 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs @@ -15,7 +15,7 @@ using Microsoft.Extensions.Hosting; using MyCompanyName.MyProjectName.EntityFrameworkCore; using MyCompanyName.MyProjectName.MultiTenancy; using StackExchange.Redis; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using Volo.Abp; using Volo.Abp.AspNetCore.Authentication.JwtBearer; using Volo.Abp.AspNetCore.Mvc; diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs index 9d28180208..66b658f09b 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/MyProjectNameHttpApiHostModule.cs @@ -12,7 +12,7 @@ using MyCompanyName.MyProjectName.EntityFrameworkCore; using MyCompanyName.MyProjectName.MultiTenancy; using Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite; using Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite.Bundling; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using OpenIddict.Validation.AspNetCore; using Volo.Abp; using Volo.Abp.Account; diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebModule.cs index 152076d235..1c7badcaf1 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebModule.cs @@ -14,7 +14,7 @@ using MyCompanyName.MyProjectName.Localization; using MyCompanyName.MyProjectName.MultiTenancy; using MyCompanyName.MyProjectName.Web.Menus; using StackExchange.Redis; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using Volo.Abp; using Volo.Abp.AspNetCore.Authentication.OpenIdConnect; using Volo.Abp.AspNetCore.Mvc.Client; diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs index 27f8f93367..155f08b603 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/MyProjectNameWebModule.cs @@ -10,7 +10,7 @@ using MyCompanyName.MyProjectName.EntityFrameworkCore; using MyCompanyName.MyProjectName.Localization; using MyCompanyName.MyProjectName.MultiTenancy; using MyCompanyName.MyProjectName.Web.Menus; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using OpenIddict.Validation.AspNetCore; using Volo.Abp; using Volo.Abp.Account.Web; diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/MyProjectNameAuthServerModule.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/MyProjectNameAuthServerModule.cs index feb60f2b34..6da496bac6 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/MyProjectNameAuthServerModule.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/MyProjectNameAuthServerModule.cs @@ -10,7 +10,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; using MyCompanyName.MyProjectName.MultiTenancy; using StackExchange.Redis; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using OpenIddict.Validation.AspNetCore; using Volo.Abp; using Volo.Abp.Account; diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/MyProjectNameBlazorHostModule.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/MyProjectNameBlazorHostModule.cs index b845e23241..452eb58b56 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/MyProjectNameBlazorHostModule.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/MyProjectNameBlazorHostModule.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using MyCompanyName.MyProjectName.Blazor.Server.Host.Components; using MyCompanyName.MyProjectName.Blazor.Server.Host.Menus; using MyCompanyName.MyProjectName.EntityFrameworkCore; diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs index 61534b55c8..dad13a4516 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs @@ -13,7 +13,7 @@ using Microsoft.Extensions.Hosting; using MyCompanyName.MyProjectName.EntityFrameworkCore; using MyCompanyName.MyProjectName.MultiTenancy; using StackExchange.Redis; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using Volo.Abp; using Volo.Abp.AspNetCore.Authentication.JwtBearer; using Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy; diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebHostModule.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebHostModule.cs index 8d912bcad4..35bc9dfaae 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebHostModule.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebHostModule.cs @@ -2,7 +2,7 @@ using System; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using System.IO; using System.Reflection; using System.Threading.Tasks; diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/MyProjectNameWebUnifiedModule.cs b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/MyProjectNameWebUnifiedModule.cs index 5bca5679e1..1612d7b95a 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/MyProjectNameWebUnifiedModule.cs +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/MyProjectNameWebUnifiedModule.cs @@ -8,7 +8,7 @@ using Microsoft.Extensions.Hosting; using MyCompanyName.MyProjectName.EntityFrameworkCore; using MyCompanyName.MyProjectName.MultiTenancy; using MyCompanyName.MyProjectName.Web; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using Swashbuckle.AspNetCore.Swagger; using Volo.Abp; using Volo.Abp.Account; From 8fedeb71667f112f4a9025d99ee79ad7a99ff8b9 Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Sat, 22 Nov 2025 15:50:17 +0800 Subject: [PATCH 08/27] Refactor Swagger security requirement setup --- .../openiddict/app/OpenIddict.Demo.API/Program.cs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/modules/openiddict/app/OpenIddict.Demo.API/Program.cs b/modules/openiddict/app/OpenIddict.Demo.API/Program.cs index 2c4d359d73..c565273532 100644 --- a/modules/openiddict/app/OpenIddict.Demo.API/Program.cs +++ b/modules/openiddict/app/OpenIddict.Demo.API/Program.cs @@ -42,19 +42,9 @@ builder.Services.AddSwaggerGen(options => } }); - options.AddSecurityRequirement(new OpenApiSecurityRequirement + options.AddSecurityRequirement(document => new OpenApiSecurityRequirement() { - { - new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Type = ReferenceType.SecurityScheme, - Id = "oauth2" - } - }, - Array.Empty() - } + [new OpenApiSecuritySchemeReference("oauth2", document)] = [] }); }); From 1be22edac9c27bcd3529910ad3ea7cc9d0fda0e9 Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 24 Nov 2025 15:37:21 +0800 Subject: [PATCH 09/27] Introduce AddAbpOptions to prevent options deadlocks Related to #24247 --- .../AbpAspNetCoreMvcNewtonsoftModule.cs | 2 +- .../AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs | 2 +- .../Mvc/Json/MvcCoreBuilderExtensions.cs | 2 +- .../ServiceCollectionOptionsExtensions.cs | 22 ++++++++++++ .../Abp/Options/AbpUnnamedOptionsManager.cs | 34 +++++++++++++++++++ .../Newtonsoft/AbpJsonNewtonsoftModule.cs | 2 +- .../AbpJsonSystemTextJsonModule.cs | 2 +- .../Volo/Abp/Json/AbpJsonTestModule.cs | 4 +-- .../Abp/MemoryDb/AbpMemoryDbTestModule.cs | 2 +- .../AspNetCore/AbpIdentityAspNetCoreModule.cs | 2 +- 10 files changed, 65 insertions(+), 9 deletions(-) create mode 100644 framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceCollectionOptionsExtensions.cs create mode 100644 framework/src/Volo.Abp.Core/Volo/Abp/Options/AbpUnnamedOptionsManager.cs diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.NewtonsoftJson/Volo/Abp/AspNetCore/Mvc/NewtonsoftJson/AbpAspNetCoreMvcNewtonsoftModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.NewtonsoftJson/Volo/Abp/AspNetCore/Mvc/NewtonsoftJson/AbpAspNetCoreMvcNewtonsoftModule.cs index 95e07a06e0..214b866197 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.NewtonsoftJson/Volo/Abp/AspNetCore/Mvc/NewtonsoftJson/AbpAspNetCoreMvcNewtonsoftModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.NewtonsoftJson/Volo/Abp/AspNetCore/Mvc/NewtonsoftJson/AbpAspNetCoreMvcNewtonsoftModule.cs @@ -13,7 +13,7 @@ public class AbpAspNetCoreMvcNewtonsoftModule : AbpModule { context.Services.AddMvcCore().AddNewtonsoftJson(); - context.Services.AddOptions() + context.Services.AddAbpOptions() .Configure((options, rootServiceProvider) => { options.SerializerSettings.ContractResolver = diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs index c3fc470ed8..ad8d8c1c28 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs @@ -165,7 +165,7 @@ public class AbpAspNetCoreMvcModule : AbpModule context.Services.AddSingleton(); context.Services.TryAddEnumerable(ServiceDescriptor.Transient()); - context.Services.AddOptions() + context.Services.AddAbpOptions() .Configure((mvcOptions, serviceProvider) => { mvcOptions.AddAbp(context.Services); diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/MvcCoreBuilderExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/MvcCoreBuilderExtensions.cs index f2bac81316..89af08fd43 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/MvcCoreBuilderExtensions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Json/MvcCoreBuilderExtensions.cs @@ -13,7 +13,7 @@ public static class MvcCoreBuilderExtensions { public static IMvcCoreBuilder AddAbpJson(this IMvcCoreBuilder builder) { - builder.Services.AddOptions() + builder.Services.AddAbpOptions() .Configure((options, rootServiceProvider) => { options.JsonSerializerOptions.ReadCommentHandling = JsonCommentHandling.Skip; diff --git a/framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceCollectionOptionsExtensions.cs b/framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceCollectionOptionsExtensions.cs new file mode 100644 index 0000000000..43230c4dbb --- /dev/null +++ b/framework/src/Volo.Abp.Core/Microsoft/Extensions/DependencyInjection/ServiceCollectionOptionsExtensions.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; +using Volo.Abp.Options; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class ServiceCollectionOptionsExtensions +{ + /// + /// You should only use this method to register options if you need to continue using the ServiceProvider to get other options in your Options configuration method. + /// Otherwise, please use the default AddOptions method for better performance. + /// + /// + /// + /// + public static OptionsBuilder AddAbpOptions(this IServiceCollection services) + where TOptions : class + { + services.TryAddSingleton, AbpUnnamedOptionsManager>(); + return services.AddOptions(); + } +} diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Options/AbpUnnamedOptionsManager.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Options/AbpUnnamedOptionsManager.cs new file mode 100644 index 0000000000..eeb46271ee --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Options/AbpUnnamedOptionsManager.cs @@ -0,0 +1,34 @@ +using Microsoft.Extensions.Options; + +namespace Volo.Abp.Options; + +/// +/// This Options manager is similar to Microsoft UnnamedOptionsManager but without the locking mechanism. +/// Prevent deadlocks when accessing options in multiple threads. +/// +/// +public class AbpUnnamedOptionsManager : IOptions + where TOptions : class +{ + private readonly IOptionsFactory _factory; + private volatile TOptions? _value; + + public AbpUnnamedOptionsManager(IOptionsFactory factory) + { + _factory = factory; + } + + public TOptions Value + { + get + { + if (_value is TOptions value) + { + return value; + } + + _value = _factory.Create(Microsoft.Extensions.Options.Options.DefaultName); + return _value; + } + } +} diff --git a/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpJsonNewtonsoftModule.cs b/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpJsonNewtonsoftModule.cs index ea35831d86..4b35ee5f25 100644 --- a/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpJsonNewtonsoftModule.cs +++ b/framework/src/Volo.Abp.Json.Newtonsoft/Volo/Abp/Json/Newtonsoft/AbpJsonNewtonsoftModule.cs @@ -10,7 +10,7 @@ public class AbpJsonNewtonsoftModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { - context.Services.AddOptions() + context.Services.AddAbpOptions() .Configure((options, rootServiceProvider) => { options.JsonSerializerSettings.ContractResolver = new AbpCamelCasePropertyNamesContractResolver( diff --git a/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/AbpJsonSystemTextJsonModule.cs b/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/AbpJsonSystemTextJsonModule.cs index 0a5066cec1..2679cca96c 100644 --- a/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/AbpJsonSystemTextJsonModule.cs +++ b/framework/src/Volo.Abp.Json.SystemTextJson/Volo/Abp/Json/SystemTextJson/AbpJsonSystemTextJsonModule.cs @@ -15,7 +15,7 @@ public class AbpJsonSystemTextJsonModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { - context.Services.AddOptions() + context.Services.AddAbpOptions() .Configure((options, rootServiceProvider) => { // If the user hasn't explicitly configured the encoder, use the less strict encoder that does not encode all non-ASCII characters. diff --git a/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpJsonTestModule.cs b/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpJsonTestModule.cs index 121ec62a85..5382d3db46 100644 --- a/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpJsonTestModule.cs +++ b/framework/test/Volo.Abp.Json.Tests/Volo/Abp/Json/AbpJsonTestModule.cs @@ -19,7 +19,7 @@ public class AbpJsonSystemTextJsonTestModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { - context.Services.AddOptions() + context.Services.AddAbpOptions() .Configure((options, rootServiceProvider) => { if (options.JsonSerializerOptions.TypeInfoResolver != null) @@ -43,7 +43,7 @@ public class AbpJsonNewtonsoftTestModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { - context.Services.AddOptions() + context.Services.AddAbpOptions() .Configure((options, rootServiceProvider) => { options.JsonSerializerSettings.ContractResolver = new AbpCamelCasePropertyNamesContractResolver( diff --git a/framework/test/Volo.Abp.MemoryDb.Tests/Volo/Abp/MemoryDb/AbpMemoryDbTestModule.cs b/framework/test/Volo.Abp.MemoryDb.Tests/Volo/Abp/MemoryDb/AbpMemoryDbTestModule.cs index 4435f1dcf0..a2234aa14a 100644 --- a/framework/test/Volo.Abp.MemoryDb.Tests/Volo/Abp/MemoryDb/AbpMemoryDbTestModule.cs +++ b/framework/test/Volo.Abp.MemoryDb.Tests/Volo/Abp/MemoryDb/AbpMemoryDbTestModule.cs @@ -34,7 +34,7 @@ public class AbpMemoryDbTestModule : AbpModule options.AddRepository(); }); - context.Services.AddOptions() + context.Services.AddAbpOptions() .Configure((options, rootServiceProvider) => { options.JsonSerializerOptions.Converters.Add(new EntityJsonConverter()); diff --git a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreModule.cs b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreModule.cs index d261068adb..67b5d0bfa7 100644 --- a/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.AspNetCore/Volo/Abp/Identity/AspNetCore/AbpIdentityAspNetCoreModule.cs @@ -47,7 +47,7 @@ public class AbpIdentityAspNetCoreModule : AbpModule public override void PostConfigureServices(ServiceConfigurationContext context) { - context.Services.AddOptions() + context.Services.AddAbpOptions() .Configure((securityStampValidatorOptions, serviceProvider) => { var abpRefreshingPrincipalOptions = serviceProvider.GetRequiredService>().Value; From 9c1bfe5228c780143512470b7e26d5e2f083c431 Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 24 Nov 2025 15:41:18 +0800 Subject: [PATCH 10/27] Remove volatile keyword from _value in AbpUnnamedOptionsManager --- .../Volo/Abp/Options/AbpUnnamedOptionsManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Options/AbpUnnamedOptionsManager.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Options/AbpUnnamedOptionsManager.cs index eeb46271ee..894df70e75 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Options/AbpUnnamedOptionsManager.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Options/AbpUnnamedOptionsManager.cs @@ -11,7 +11,7 @@ public class AbpUnnamedOptionsManager : IOptions where TOptions : class { private readonly IOptionsFactory _factory; - private volatile TOptions? _value; + private TOptions? _value; public AbpUnnamedOptionsManager(IOptionsFactory factory) { @@ -22,7 +22,7 @@ public class AbpUnnamedOptionsManager : IOptions { get { - if (_value is TOptions value) + if (_value is { } value) { return value; } From 2f61a755fb34092a219808906e3897bde2eb5cdf Mon Sep 17 00:00:00 2001 From: sumeyye Date: Mon, 24 Nov 2025 14:22:43 +0300 Subject: [PATCH 11/27] update: ai-tool usage examples --- .../src/commands/ai-config/index.ts | 41 +++++++++++-------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/npm/ng-packs/packages/schematics/src/commands/ai-config/index.ts b/npm/ng-packs/packages/schematics/src/commands/ai-config/index.ts index 1256927a86..2cc2da1165 100644 --- a/npm/ng-packs/packages/schematics/src/commands/ai-config/index.ts +++ b/npm/ng-packs/packages/schematics/src/commands/ai-config/index.ts @@ -1,4 +1,14 @@ -import { Rule, SchematicsException, Tree, apply, url, mergeWith, MergeStrategy, filter, chain } from '@angular-devkit/schematics'; +import { + Rule, + SchematicsException, + Tree, + apply, + url, + mergeWith, + MergeStrategy, + filter, + chain, +} from '@angular-devkit/schematics'; import { join, normalize } from '@angular-devkit/core'; import { AiConfigSchema, AiTool } from './model'; import { getWorkspace } from '../../utils'; @@ -10,21 +20,21 @@ export default function (options: AiConfigSchema): Rule { 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="claude, cursor"'); + console.log(' ng g @abp/ng.schematics:ai-config --tool=gemini --tool=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,]+/) - .filter(t => t) as AiTool[]; + const tools = options.tool.split(/[\s,]+/).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(', ')}` + `Invalid AI tool(s): ${invalidTools.join(', ')}. Valid options are: ${validTools.join(', ')}`, ); } @@ -40,9 +50,7 @@ export default function (options: AiConfigSchema): Rule { const trimmedTargetProject = options.targetProject.trim(); const project = workspace.projects.get(trimmedTargetProject); if (!project) { - throw new SchematicsException( - `Project "${trimmedTargetProject}" not found in workspace.` - ); + throw new SchematicsException(`Project "${trimmedTargetProject}" not found in workspace.`); } targetPath = normalize(project.root); } @@ -51,24 +59,25 @@ export default function (options: AiConfigSchema): Rule { console.log(`📁 Target path: ${targetPath}`); console.log(`🤖 Selected tools: ${tools.join(', ')}`); - const rules: Rule[] = tools - .map(tool => generateConfigForTool(tool, targetPath, options.overwrite || false)); + const rules: Rule[] = tools.map(tool => + generateConfigForTool(tool, targetPath, options.overwrite || false), + ); return chain([ ...rules, (tree: Tree) => { console.log('✅ AI configuration files generated successfully!'); console.log('\n📝 Generated files:'); - + tools.forEach(tool => { const configPath = getConfigPath(tool, targetPath); console.log(` - ${configPath}`); }); console.log('\n💡 Tip: Restart your IDE or AI tool to apply the new configurations.'); - + return tree; - } + }, ]); }; } @@ -76,7 +85,7 @@ export default function (options: AiConfigSchema): Rule { function generateConfigForTool(tool: AiTool, targetPath: string, overwrite: boolean): Rule { return (tree: Tree) => { const configPath = getConfigPath(tool, targetPath); - + if (tree.exists(configPath) && !overwrite) { console.log(`⚠️ Configuration file already exists: ${configPath}`); console.log(` Use --overwrite flag to replace existing files.`); @@ -87,7 +96,7 @@ function generateConfigForTool(tool: AiTool, targetPath: string, overwrite: bool const source = apply(url(sourceDir), [ filter(path => { return !path.endsWith('.DS_Store'); - }) + }), ]); return mergeWith(source, overwrite ? MergeStrategy.Overwrite : MergeStrategy.Default); @@ -101,7 +110,7 @@ function getConfigPath(tool: AiTool, basePath: string): string { cursor: '.cursor/rules/cursor.mdc', gemini: '.gemini/GEMINI.md', junie: '.junie/guidelines.md', - windsurf: '.windsurf/rules/guidelines.md' + windsurf: '.windsurf/rules/guidelines.md', }; const configFile = configFiles[tool]; From 04e5d4da5941c2085dd37d37380724940468eca7 Mon Sep 17 00:00:00 2001 From: Fahri Gedik Date: Tue, 25 Nov 2025 19:25:42 +0300 Subject: [PATCH 12/27] Add redirectUrl support to PermissionGuard PermissionGuard now supports redirecting to a specified URL when access is denied and a redirectUrl is provided in route data. Updated guard logic to use map instead of filter/tap, and added corresponding unit tests to verify redirect behavior. --- .../core/src/lib/guards/permission.guard.ts | 31 ++++++++++++++----- .../src/lib/tests/permission.guard.spec.ts | 17 ++++++++++ 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/guards/permission.guard.ts b/npm/ng-packs/packages/core/src/lib/guards/permission.guard.ts index 8d1e478864..3b77913d2e 100644 --- a/npm/ng-packs/packages/core/src/lib/guards/permission.guard.ts +++ b/npm/ng-packs/packages/core/src/lib/guards/permission.guard.ts @@ -4,10 +4,11 @@ import { CanActivateFn, Router, RouterStateSnapshot, + UrlTree, } from '@angular/router'; import { HttpErrorResponse } from '@angular/common/http'; import { Observable, of } from 'rxjs'; -import { filter, take, tap } from 'rxjs/operators'; +import { map, take } from 'rxjs/operators'; import { AuthService, IAbpGuard } from '../abstracts'; import { findRoute, getRoutePath } from '../utils/route-utils'; import { RoutesService, PermissionService, HttpErrorReporterService } from '../services'; @@ -25,7 +26,7 @@ export class PermissionGuard implements IAbpGuard { protected readonly permissionService = inject(PermissionService); protected readonly httpErrorReporter = inject(HttpErrorReporterService); - canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { let { requiredPolicy } = route.data || {}; if (!requiredPolicy) { @@ -38,12 +39,19 @@ export class PermissionGuard implements IAbpGuard { } return this.permissionService.getGrantedPolicy$(requiredPolicy).pipe( - filter(Boolean), take(1), - tap(access => { - if (!access && this.authService.isAuthenticated) { + map(access => { + if (access) return true; + + if (route.data?.['redirectUrl']) { + return this.router.parseUrl(route.data['redirectUrl']); + } + + if (this.authService.isAuthenticated) { this.httpErrorReporter.reportError({ status: 403 } as HttpErrorResponse); } + + return false; }), ); } @@ -77,12 +85,19 @@ export const permissionGuard: CanActivateFn = ( } return permissionService.getGrantedPolicy$(requiredPolicy).pipe( - filter(Boolean), take(1), - tap(access => { - if (!access && authService.isAuthenticated) { + map(access => { + if (access) return true; + + if (route.data?.['redirectUrl']) { + return router.parseUrl(route.data['redirectUrl']); + } + + if (authService.isAuthenticated) { httpErrorReporter.reportError({ status: 403 } as HttpErrorResponse); } + + return false; }), ); }; diff --git a/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts index 1a0ae3e147..45f3b46d80 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts @@ -39,6 +39,15 @@ describe('authGuard', () => { component: DummyComponent, canActivate: [permissionGuard], }, + { + path: 'redirect-test', + component: DummyComponent, + canActivate: [permissionGuard], + data: { + requiredPolicy: 'TestPolicy', + redirectUrl: '/zibzib', + }, + }, ]; beforeEach(() => { @@ -103,4 +112,12 @@ describe('authGuard', () => { await RouterTestingHarness.create('/zibzib'); expect(TestBed.inject(Router).url).toEqual('/zibzib'); }); + + it('should redirect to redirectUrl when the grantedPolicy is false and redirectUrl is provided', async () => { + permissionService.getGrantedPolicy$.andReturn(of(false)); + await RouterTestingHarness.create('/redirect-test'); + + expect(TestBed.inject(Router).url).toEqual('/zibzib'); + expect(httpErrorReporter.reportError).not.toHaveBeenCalled(); + }); }); From 455e2cb2c5466e0325dabf90b87c7d9c917275a4 Mon Sep 17 00:00:00 2001 From: berkansasmaz Date: Thu, 27 Nov 2025 09:29:17 +0300 Subject: [PATCH 13/27] Use IServiceScopeFactory to resolve service due to dispose problem --- .../Internal/Telemetry/TelemetryService.cs | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/TelemetryService.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/TelemetryService.cs index 8a2e3b4382..2f684acd29 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/TelemetryService.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/TelemetryService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp.DependencyInjection; using Volo.Abp.Internal.Telemetry.Activity.Contracts; using Volo.Abp.Internal.Telemetry.Constants; @@ -11,17 +12,11 @@ namespace Volo.Abp.Internal.Telemetry; public class TelemetryService : ITelemetryService, IScopedDependency { - private readonly ITelemetryActivitySender _telemetryActivitySender; - private readonly ITelemetryActivityEventBuilder _telemetryActivityEventBuilder; - private readonly ITelemetryActivityStorage _telemetryActivityStorage; + private readonly IServiceScopeFactory _serviceScopeFactory; - public TelemetryService(ITelemetryActivitySender telemetryActivitySender, - ITelemetryActivityEventBuilder telemetryActivityEventBuilder, - ITelemetryActivityStorage telemetryActivityStorage) + public TelemetryService(IServiceScopeFactory serviceScopeFactory) { - _telemetryActivitySender = telemetryActivitySender; - _telemetryActivityEventBuilder = telemetryActivityEventBuilder; - _telemetryActivityStorage = telemetryActivityStorage; + _serviceScopeFactory = serviceScopeFactory; } @@ -74,24 +69,37 @@ public class TelemetryService : ITelemetryService, IScopedDependency { _ = Task.Run(async () => { - await BuildAndSendActivityAsync(context); + using var scope = _serviceScopeFactory.CreateScope(); + + var telemetryActivityEventBuilder = scope.ServiceProvider.GetRequiredService(); + var telemetryActivityStorage = scope.ServiceProvider.GetRequiredService(); + var telemetryActivitySender = scope.ServiceProvider.GetRequiredService(); + + await BuildAndSendActivityAsync(context, + telemetryActivityEventBuilder, + telemetryActivityStorage, + telemetryActivitySender); }); return Task.CompletedTask; } - private async Task BuildAndSendActivityAsync(ActivityContext context) + private static async Task BuildAndSendActivityAsync( + ActivityContext context, + ITelemetryActivityEventBuilder telemetryActivityEventBuilder, + ITelemetryActivityStorage telemetryActivityStorage, + ITelemetryActivitySender telemetryActivitySender) { try { - var activityEvent = await _telemetryActivityEventBuilder.BuildAsync(context); + var activityEvent = await telemetryActivityEventBuilder.BuildAsync(context); if (activityEvent is null) { return; } - _telemetryActivityStorage.SaveActivity(activityEvent); - await _telemetryActivitySender.TrySendQueuedActivitiesAsync(); + telemetryActivityStorage.SaveActivity(activityEvent); + await telemetryActivitySender.TrySendQueuedActivitiesAsync(); } catch { From 4506f06fc667c3b645fd2bfc374835b5c3051f75 Mon Sep 17 00:00:00 2001 From: berkansasmaz Date: Thu, 27 Nov 2025 09:29:40 +0300 Subject: [PATCH 14/27] AllowTrailingCommas while parse jsons for telemetry --- .../Providers/TelemetrySolutionInfoEnricher.cs | 5 ++++- .../Detectors/AbpStudioDetector.cs | 10 ++++++++-- .../Detectors/VisualStudioCodeDetector.cs | 10 ++++++++-- .../Telemetry/Helpers/AbpPackageMetadataReader.cs | 6 ++++-- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetrySolutionInfoEnricher.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetrySolutionInfoEnricher.cs index e01768c418..c57d51e6f2 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetrySolutionInfoEnricher.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Providers/TelemetrySolutionInfoEnricher.cs @@ -114,7 +114,10 @@ internal sealed class TelemetrySolutionInfoEnricher : TelemetryActivityEventEnri } var moduleJsonFileContent = File.ReadAllText(modulePath); - using var moduleDoc = JsonDocument.Parse(moduleJsonFileContent); + using var moduleDoc = JsonDocument.Parse(moduleJsonFileContent, new JsonDocumentOptions + { + AllowTrailingCommas = true + }); if (!moduleDoc.RootElement.TryGetProperty("imports", out var imports)) { diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/AbpStudioDetector.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/AbpStudioDetector.cs index 3d35c7192a..b37760cb04 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/AbpStudioDetector.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/AbpStudioDetector.cs @@ -40,7 +40,10 @@ internal sealed class AbpStudioDetector : SoftwareDetector return null; } using var fs = new FileStream(ideStateJsonPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - using var doc = JsonDocument.Parse(fs); + using var doc = JsonDocument.Parse(fs, new JsonDocumentOptions + { + AllowTrailingCommas = true + }); return doc.RootElement.TryGetProperty("theme", out var themeElement) ? themeElement.GetString() : null; } @@ -55,7 +58,10 @@ internal sealed class AbpStudioDetector : SoftwareDetector } using var fs = new FileStream(extensionsFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - using var doc = JsonDocument.Parse(fs); + using var doc = JsonDocument.Parse(fs, new JsonDocumentOptions + { + AllowTrailingCommas = true + }); if (doc.RootElement.TryGetProperty("Extensions", out var extensionsElement) && extensionsElement.ValueKind == JsonValueKind.Array) diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/VisualStudioCodeDetector.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/VisualStudioCodeDetector.cs index 72fda0f500..52559663c2 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/VisualStudioCodeDetector.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/EnvironmentInspection/Detectors/VisualStudioCodeDetector.cs @@ -77,7 +77,10 @@ internal sealed class VisualStudioCodeDetector : SoftwareDetector { try { - using var jsonDoc = JsonDocument.Parse(File.ReadAllText(productJson)); + using var jsonDoc = JsonDocument.Parse(File.ReadAllText(productJson), new JsonDocumentOptions + { + AllowTrailingCommas = true + }); var root = jsonDoc.RootElement; if (root.TryGetProperty("version", out var versionProp)) { @@ -105,7 +108,10 @@ internal sealed class VisualStudioCodeDetector : SoftwareDetector { try { - using var json = JsonDocument.Parse( File.ReadAllText(settingsPath)); + using var json = JsonDocument.Parse( File.ReadAllText(settingsPath), new JsonDocumentOptions + { + AllowTrailingCommas = true + }); var root = json.RootElement; if (root.TryGetProperty("theme", out var themeProp)) { diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Helpers/AbpPackageMetadataReader.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Helpers/AbpPackageMetadataReader.cs index f9ce98a2e5..446462c527 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Helpers/AbpPackageMetadataReader.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Helpers/AbpPackageMetadataReader.cs @@ -49,11 +49,13 @@ static internal class AbpProjectMetadataReader private static AbpProjectMetaData ReadOrCreateMetadata(string packagePath) { - var fileContent = File.ReadAllText(packagePath); var metadata = new AbpProjectMetaData(); - using var document = JsonDocument.Parse(fileContent); + using var document = JsonDocument.Parse(fileContent, new JsonDocumentOptions + { + AllowTrailingCommas = true + }); var root = document.RootElement; if (TryGetProjectId(root,out var projectId)) From fc4d5636988c5f8337c2ee468e7b095276220132 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ahmet=20=C3=87elik?= Date: Thu, 27 Nov 2025 15:10:21 +0300 Subject: [PATCH 15/27] Update Index.cshtml --- .../Pages/Docs/Admin/Documents/Index.cshtml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/Index.cshtml b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/Index.cshtml index 899a8e6473..89ee5db51e 100644 --- a/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/Index.cshtml +++ b/modules/docs/src/Volo.Docs.Admin.Web/Pages/Docs/Admin/Documents/Index.cshtml @@ -29,7 +29,7 @@
- +
@@ -86,7 +86,7 @@
- + @@ -149,7 +149,7 @@ - +
@L["Format"].Value
@@ -161,9 +161,6 @@
- - -
@L["LastSignificantUpdateTime"].Value
@@ -185,7 +182,9 @@ - + + + From 5bf657ab45d79db8c4fdb88457677e63eff0ed59 Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 28 Nov 2025 09:30:02 +0800 Subject: [PATCH 16/27] Add IsReadOnly and IsVisible to extension property modals --- .../ExtensionProperties.razor.cs | 21 +++++++++++++++++-- .../ExtensionPropertyUiFormConfiguration.cs | 5 +++++ .../ModuleExtensionConfigurationHelper.cs | 4 ++++ .../ObjectExtensionPropertyInfo.cs | 18 ++++++++++++++++ 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionProperties.razor.cs b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionProperties.razor.cs index 27f69f3ba4..be9839b612 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionProperties.razor.cs +++ b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionProperties.razor.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Immutable; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Components; using Microsoft.Extensions.Localization; @@ -29,8 +30,24 @@ public partial class ExtensionProperties : Component public ImmutableList Properties { get; set; } = ImmutableList.Empty; - protected async override Task OnInitializedAsync() + protected override async Task OnInitializedAsync() { - Properties = await ObjectExtensionManager.Instance.GetPropertiesAndCheckPolicyAsync(ServiceProvider); + var properties = (await ObjectExtensionManager.Instance.GetPropertiesAndCheckPolicyAsync(ServiceProvider)).ToList(); + + switch (ModalType) + { + case ExtensionPropertyModalType.CreateModal: + properties = properties + .Where(p => p.UI.CreateModal.IsVisible) + .ToList(); + break; + case ExtensionPropertyModalType.EditModal: + properties = properties + .Where(p => p.UI.EditModal.IsVisible) + .ToList(); + break; + } + + Properties = properties.ToImmutableList(); } } diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ExtensionPropertyUiFormConfiguration.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ExtensionPropertyUiFormConfiguration.cs index d1d041a9b1..9a95cd6481 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ExtensionPropertyUiFormConfiguration.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ExtensionPropertyUiFormConfiguration.cs @@ -6,4 +6,9 @@ public class ExtensionPropertyUiFormConfiguration /// Default: true. /// public bool IsVisible { get; set; } = true; + + /// + /// Default: false. + /// + public bool IsReadOnly { get; set; } = false; } diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ModuleExtensionConfigurationHelper.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ModuleExtensionConfigurationHelper.cs index 9a13b17028..40b9e8feb6 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ModuleExtensionConfigurationHelper.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/Modularity/ModuleExtensionConfigurationHelper.cs @@ -175,6 +175,10 @@ public static class ModuleExtensionConfigurationHelper property.DefaultValueFactory = propertyConfig.DefaultValueFactory; property.Lookup = propertyConfig.UI.Lookup; property.UI.Order = propertyConfig.UI.Order; + property.UI.CreateModal.IsVisible = propertyConfig.UI.OnCreateForm.IsVisible; + property.UI.CreateModal.IsReadOnly = propertyConfig.UI.OnCreateForm.IsReadOnly; + property.UI.EditModal.IsVisible = propertyConfig.UI.OnEditForm.IsVisible; + property.UI.EditModal.IsReadOnly = propertyConfig.UI.OnEditForm.IsReadOnly; property.Policy = propertyConfig.Policy; foreach (var configuration in propertyConfig.Configuration) { diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs index e5ed79b99e..35c40e8043 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs @@ -90,16 +90,34 @@ public class ObjectExtensionPropertyInfo : IHasNameWithLocalizableDisplayName, I { public int Order { get; set; } + public ExtensionPropertyUICreateModal CreateModal { get; set; } + public ExtensionPropertyUIEditModal EditModal { get; set; } public ExtensionPropertyUI() { + CreateModal = new ExtensionPropertyUICreateModal(); EditModal = new ExtensionPropertyUIEditModal(); } } + public class ExtensionPropertyUICreateModal + { + /// + /// Default: true. + /// + public bool IsVisible { get; set; } = true; + + public bool IsReadOnly { get; set; } + } + public class ExtensionPropertyUIEditModal { + /// + /// Default: true. + /// + public bool IsVisible { get; set; } = true; + public bool IsReadOnly { get; set; } } } From f8a22e37b18c03a8cd0443068fd62e9c056bd2fe Mon Sep 17 00:00:00 2001 From: maliming Date: Fri, 28 Nov 2025 09:39:02 +0800 Subject: [PATCH 17/27] Refactor property filtering in OnInitializedAsync --- .../ExtensionProperties.razor.cs | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionProperties.razor.cs b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionProperties.razor.cs index be9839b612..b840119d18 100644 --- a/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionProperties.razor.cs +++ b/framework/src/Volo.Abp.BlazoriseUI/Components/ObjectExtending/ExtensionProperties.razor.cs @@ -32,22 +32,11 @@ public partial class ExtensionProperties : Component protected override async Task OnInitializedAsync() { - var properties = (await ObjectExtensionManager.Instance.GetPropertiesAndCheckPolicyAsync(ServiceProvider)).ToList(); - - switch (ModalType) - { - case ExtensionPropertyModalType.CreateModal: - properties = properties - .Where(p => p.UI.CreateModal.IsVisible) - .ToList(); - break; - case ExtensionPropertyModalType.EditModal: - properties = properties - .Where(p => p.UI.EditModal.IsVisible) - .ToList(); - break; - } - - Properties = properties.ToImmutableList(); + Properties = + (await ObjectExtensionManager.Instance.GetPropertiesAndCheckPolicyAsync(ServiceProvider)) + .Where(p => ModalType == ExtensionPropertyModalType.CreateModal + ? p.UI.CreateModal.IsVisible + : p.UI.EditModal.IsVisible) + .ToImmutableList(); } } From 3faae2cf148235547aba3f94a4acc385b648bd27 Mon Sep 17 00:00:00 2001 From: berkansasmaz Date: Fri, 28 Nov 2025 16:43:23 +0300 Subject: [PATCH 18/27] Sends telemetry for only server-side applications --- .../src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs b/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs index 04ea98644e..7d466004f4 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs @@ -200,7 +200,13 @@ public abstract class AbpApplicationBase : IAbpApplication using var scope = ServiceProvider.CreateScope(); var abpHostEnvironment = scope.ServiceProvider.GetRequiredService(); var configuration = scope.ServiceProvider.GetRequiredService(); - return abpHostEnvironment.IsDevelopment() && configuration.GetValue("Abp:Telemetry:IsEnabled") != false; + + if (OperatingSystem.IsMacOS() || OperatingSystem.IsLinux() || OperatingSystem.IsWindows()) + { + return abpHostEnvironment.IsDevelopment() && configuration.GetValue("Abp:Telemetry:IsEnabled") != false; + } + + return false; } //TODO: We can extract a new class for this From 6b1fec92096251c92ffe7dfa175dba3fe6a5c692 Mon Sep 17 00:00:00 2001 From: berkansasmaz Date: Fri, 28 Nov 2025 16:43:40 +0300 Subject: [PATCH 19/27] Delete old telemetry information if exist --- .../Storage/TelemetryActivityStorage.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Storage/TelemetryActivityStorage.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Storage/TelemetryActivityStorage.cs index 6796af7179..b37ec15d71 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Storage/TelemetryActivityStorage.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Activity/Storage/TelemetryActivityStorage.cs @@ -25,6 +25,8 @@ public class TelemetryActivityStorage : ITelemetryActivityStorage, ISingletonDep public TelemetryActivityStorage() { CreateDirectoryIfNotExist(); + + DeleteExistingOldInformation(); State = LoadState(); } @@ -166,6 +168,22 @@ public class TelemetryActivityStorage : ITelemetryActivityStorage, ISingletonDep } } + private static void DeleteExistingOldInformation() + { + try + { + var file = new FileInfo(TelemetryPaths.ActivityStorage); + if (file.Exists && file.CreationTime < new DateTime(2025, 12, 01)) + { + file.Delete(); + } + } + catch + { + // Ignored + } + } + private static TelemetryActivityStorageState LoadState() { try From 21937b1220c533e637761fc4b5dac43f9097a6d1 Mon Sep 17 00:00:00 2001 From: berkansasmaz Date: Fri, 28 Nov 2025 16:43:52 +0300 Subject: [PATCH 20/27] Add UserDeviceId to ActivityPropertyNames --- .../Abp/Internal/Telemetry/Constants/ActivityPropertyNames.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/ActivityPropertyNames.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/ActivityPropertyNames.cs index 924cd1dd7d..0157c61bb8 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/ActivityPropertyNames.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/ActivityPropertyNames.cs @@ -12,6 +12,7 @@ public static class ActivityPropertyNames public const string IpAddress = "IpAddress"; public const string IsFirstSession = "IsFirstSession"; public const string DeviceId = "DeviceId"; + public const string UserDeviceId = "UserDeviceId"; public const string DeviceLanguage = "DeviceLanguage"; public const string OperatingSystem = "OperatingSystem"; public const string CountryIsoCode = "CountryIsoCode"; From a5637ea38a5fe5cf3f1eec1e2e7346cb96057e12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SAL=C4=B0H=20=C3=96ZKARA?= <58659931+salihozkara@users.noreply.github.com> Date: Mon, 1 Dec 2025 09:26:07 +0300 Subject: [PATCH 21/27] Use baseboard serial number for Windows device ID Replaces the use of processor ID with baseboard serial number when retrieving the unique device identifier on Windows. This change improves reliability and consistency of device identification. --- .../Abp/Internal/Telemetry/Constants/DeviceManager.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/DeviceManager.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/DeviceManager.cs index e3993886a0..393f974e98 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/DeviceManager.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/DeviceManager.cs @@ -133,7 +133,7 @@ static internal class DeviceManager { try { - return GetProcessorIdForWindows(); + return GetBaseBoardSerialNumberForWindows(); } catch { @@ -142,10 +142,10 @@ static internal class DeviceManager return GetWindowsMachineUniqueId(); } - private static string GetProcessorIdForWindows() + private static string GetBaseBoardSerialNumberForWindows() { using (var managementObjectSearcher = - new System.Management.ManagementObjectSearcher("SELECT ProcessorId FROM Win32_Processor")) + new System.Management.ManagementObjectSearcher("SELECT SerialNumber FROM Win32_BaseBoard")) { using (var searcherObj = managementObjectSearcher.Get()) { @@ -156,7 +156,7 @@ static internal class DeviceManager var managementObjectEnumerator = searcherObj.GetEnumerator(); managementObjectEnumerator.MoveNext(); - return managementObjectEnumerator.Current.GetPropertyValue("ProcessorId").ToString()!; + return managementObjectEnumerator.Current.GetPropertyValue("SerialNumber").ToString()!; } } } From 488defd9de013532c07765e2ea57347efc68041b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ahmet=20=C3=87elik?= Date: Mon, 1 Dec 2025 13:22:12 +0300 Subject: [PATCH 22/27] Update en.json --- .../AbpIoLocalization/Www/Localization/Resources/en.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json index 18730b51d6..981500451a 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json @@ -1553,6 +1553,15 @@ "IntegrateToYourKubernetesCluster_Description1": "Connect your local development environment to a local or remote Kubernetes cluster, where that cluster already runs your microservice solution.", "IntegrateToYourKubernetesCluster_Description2": "Access any service in Kubernetes with their service name as DNS, just like they are running in your local computer.", "IntegrateToYourKubernetesCluster_Description3": "Intercept any service in that cluster, so all the traffic to the intercepted service is automatically redirected to your service that is running in your local machine. When your service needs to use any service in Kubernetes, the traffic is redirected back to the cluster, just like your local service is running inside the Kubernetes.", + "AskOurAiAssistant": "Ask Our AI Assistant", + "AskOurAiAssistant_Description1": "Build faster with an AI that actually understands your ABP project. The ABP AI Assistant answers your technical questions, explains your code, and helps you solve problems directly inside ABP Studio — with full awareness of your project’s structure. You can even send screenshots or code files to get precise, context-based guidance.", + "AskOurAiAssistant_Description2": "What It Helps You Do", + "AskOurAiAssistant_Description3": "Ask anything about your ABP project — domain layer, modules, configuration, entities, services, or UI.", + "AskOurAiAssistant_Description4": "Get smart, code-aware explanations tailored to your solution.", + "AskOurAiAssistant_Description5": "Generate snippets and scaffolding suggestions instantly.", + "AskOurAiAssistant_Description6": "Fix errors faster with context-aware debugging support.", + "AskOurAiAssistant_Description7": "Learn ABP best practices as you build.", + "AskOurAiAssistant_Description8": "Whether you're generating new features, debugging an issue, or exploring a module, the AI Assistant gives you actionable, project-specific answers — right when you need them.", "GetInformed": "Get Informed", "Studio_GetInformed_Description1": "Leave your contact information to get informed and try it first when ABP Studio has been launched.", "Studio_GetInformed_Description2": "Planned preview release date: Q3 of 2023.", From 7393cf3e2969c87738ca5b3f3f29101ac34e7409 Mon Sep 17 00:00:00 2001 From: Fahri Gedik Date: Mon, 1 Dec 2025 14:04:54 +0300 Subject: [PATCH 23/27] Clarify SSR setup instructions in article Reworded the SSR setup command instructions for improved clarity, replacing 'Or using the short form' with 'Alternatively, you can use the short form'. --- .../2025-11-15-Announcing-SSR-Support/article.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/Community-Articles/2025-11-15-Announcing-SSR-Support/article.md b/docs/en/Community-Articles/2025-11-15-Announcing-SSR-Support/article.md index 51688dc5a7..ba7f0696be 100644 --- a/docs/en/Community-Articles/2025-11-15-Announcing-SSR-Support/article.md +++ b/docs/en/Community-Articles/2025-11-15-Announcing-SSR-Support/article.md @@ -33,7 +33,7 @@ You can easily add SSR support to your existing ABP Angular application using th # Generate SSR configuration for your project ng generate @abp/ng.schematics:ssr-add -# Or using the short form +# Alternatively, you can use the short form ng g @abp/ng.schematics:ssr-add ``` From 6c73902d772b73a9096d645ad12c2d32609b6422 Mon Sep 17 00:00:00 2001 From: Engincan VESKE Date: Mon, 1 Dec 2025 13:58:37 +0000 Subject: [PATCH 24/27] Updated release date and announcement link for version 10.0 --- docs/en/release-info/release-notes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/release-info/release-notes.md b/docs/en/release-info/release-notes.md index f0c58f0013..bf335fa7a2 100644 --- a/docs/en/release-info/release-notes.md +++ b/docs/en/release-info/release-notes.md @@ -14,9 +14,9 @@ Also see the following notes about ABP releases: * [ABP Studio release notes](../studio/release-notes.md) * [Change logs for ABP pro packages](https://abp.io/pro-releases) -## 10.0 (2025-10-01) +## 10.0 (2025-11-18) -This is currently a RC (release-candidate) and you can see the detailed **[blog post / announcement](https://abp.io/community/announcements/announcing-abp-10-0-release-candidate-86lrnyox)** for the v10.0 release. +See the detailed **[blog post / announcement](https://abp.io/community/announcements/abp.io-platform-10.0-final-has-been-released-spknn925)** for the v10.0 release. * Upgraded to .NET 10.0 * Upgraded to `Blazorise` **v1.8.6** From c559ad8ce215ce10efbadce9afa93a518006b431 Mon Sep 17 00:00:00 2001 From: berkansasmaz Date: Tue, 2 Dec 2025 09:23:46 +0300 Subject: [PATCH 25/27] Fix build error --- framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs b/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs index 7d466004f4..4ec967ed01 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Runtime.InteropServices; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.Extensions.Configuration; @@ -201,7 +202,7 @@ public abstract class AbpApplicationBase : IAbpApplication var abpHostEnvironment = scope.ServiceProvider.GetRequiredService(); var configuration = scope.ServiceProvider.GetRequiredService(); - if (OperatingSystem.IsMacOS() || OperatingSystem.IsLinux() || OperatingSystem.IsWindows()) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { return abpHostEnvironment.IsDevelopment() && configuration.GetValue("Abp:Telemetry:IsEnabled") != false; } From 2c7d4174a993cc00e641b595f6bec6a1713228d9 Mon Sep 17 00:00:00 2001 From: Ma Liming Date: Tue, 2 Dec 2025 16:33:44 +0800 Subject: [PATCH 26/27] Update part-01.md --- docs/en/tutorials/microservice/part-01.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/tutorials/microservice/part-01.md b/docs/en/tutorials/microservice/part-01.md index a3529019a5..7006226e21 100644 --- a/docs/en/tutorials/microservice/part-01.md +++ b/docs/en/tutorials/microservice/part-01.md @@ -25,7 +25,7 @@ } ```` -Follow the *[Get Started](../../get-started/microservice.md)* guide to create a new layered web application with the following configurations: +Follow the *[Get Started](../../get-started/microservice.md)* guide to create a new microservice solution with the following configurations: * **Solution name**: `CloudCrm` {{if DB == "EF"}} @@ -82,4 +82,4 @@ You can see the *[Microservice Solution Template](../../solution-templates/micro ## Summary -In this part, you've created the initial microservice solution, which already contains a few infrastructure services. We will create our first business service in the [next part](part-02.md). \ No newline at end of file +In this part, you've created the initial microservice solution, which already contains a few infrastructure services. We will create our first business service in the [next part](part-02.md). From 06f16a044260eaa4de1c229ec1e0bb47feb2075d Mon Sep 17 00:00:00 2001 From: Engincan VESKE Date: Wed, 3 Dec 2025 06:54:45 +0000 Subject: [PATCH 27/27] Remove the old-cli note from the CLI documentation --- docs/en/cli/index.md | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/docs/en/cli/index.md b/docs/en/cli/index.md index 7decaa658c..3a3d0c2304 100644 --- a/docs/en/cli/index.md +++ b/docs/en/cli/index.md @@ -7,11 +7,7 @@ # ABP CLI -ABP CLI (Command Line Interface) is a command line tool to perform some common operations for ABP based solutions or ABP Studio features. - -> With **v8.2+**, the old/legacy ABP CLI has been replaced with a new CLI system to align with the new templating system and [ABP Studio](../studio/index.md). The new ABP CLI commands are explained in this documentation. However, if you want to learn more about the differences between the old and new CLIs, want to learn the reason for the change, or need guidance to use the old ABP CLI, please refer to the [Old vs New CLI](differences-between-old-and-new-cli.md) documentation. -> -> You may need to remove the Old CLI before installing the New CLI, by running the following command: `dotnet tool uninstall -g Volo.Abp.Cli` +ABP CLI (Command Line Interface) is a command line tool to perform some common operations for ABP based solutions or [ABP Studio](../studio/index.md) features. ## Installation @@ -29,16 +25,16 @@ dotnet tool update -g Volo.Abp.Studio.Cli ## Global Options -While each command may have a set of options, there are some global options that can be used with any command; +While each command may have a set of options, there are some global options that can be used with any command: -* `--skip-cli-version-check` or `-scvc`: Skips to check the latest version of the ABP CLI. If you don't specify, it will check the latest version and shows a warning message if there is a newer version of the ABP CLI. -- `--skip-extension-version-check` or `-sevc`: Skips to check the latest version of the ABP CLI extensions. If you don't specify, it will check the latest version and download the latest version if there is a newer version of the ABP CLI extensions. +* `--skip-cli-version-check` or `-scvc`: Skips checking the latest version of the ABP CLI. If you don't specify, it will check the latest version and shows a warning message if there is a newer version of the ABP CLI. +- `--skip-extension-version-check` or `-sevc`: Skips checking the latest version of the ABP CLI extensions. If you don't specify, it will check the latest version and download the latest version if there is a newer version of the ABP CLI extensions. * `--old`: ABP CLI has two variations: `Volo.Abp.Studio.Cli` and `Volo.Abp.Cli`. New features/templates are added to the `Volo.Abp.Studio.Cli`. But if you want to use the old version, you can use this option **at the end of your commands**. For example, `abp new Acme.BookStore --old`. * `--help` or `-h`: Shows help for the specified command. ## Commands -Here, is the list of all available commands before explaining their details: +Here is the list of all available commands before explaining their details: * **[`help`](../cli#help)**: Shows help on the usage of the ABP CLI. * **[`cli`](../cli#cli)**: Update or remove ABP CLI.