diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000000..1eff521897 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "Bash(yarn nx g:*)", + "Bash(npx vitest:*)" + ] + } +} diff --git a/.gitignore b/.gitignore index a386e86320..d3bc52cfb8 100644 --- a/.gitignore +++ b/.gitignore @@ -328,4 +328,4 @@ deploy/_run_all_log.txt # No commit yarn.lock files in the subfolders of templates directory templates/**/yarn.lock templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/Logs/logs.txt -templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Web/Properties/launchSettings.json +templates/module/aspnet-core/src/MyCompanyName.MyProjectName.Web/Properties/launchSettings.json \ No newline at end of file diff --git a/Directory.Packages.props b/Directory.Packages.props index e28e2e49c5..89f27ea6a8 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -30,6 +30,7 @@ + @@ -140,7 +141,7 @@ - + @@ -195,4 +196,4 @@ - + \ No newline at end of file diff --git a/ai-rules/README.md b/ai-rules/README.md new file mode 100644 index 0000000000..ed9370b591 --- /dev/null +++ b/ai-rules/README.md @@ -0,0 +1,151 @@ +# ABP AI Rules + +This folder contains AI rules (Cursor `.mdc` format) for ABP based solutions. These rules help AI assistants understand ABP-specific patterns, conventions, and best practices when working with ABP-based applications. + +## Purpose + +This folder serves as a central repository for ABP-specific AI rules. The community can contribute, improve, and maintain these rules collaboratively. + +When you create a new ABP solution, these rules are included in your project based on your configuration. This provides AI assistants with ABP-specific context, helping them generate code that follows ABP conventions. + +> **Important**: These rules are ABP-specific. They don't cover general .NET or ASP.NET Core patterns—AI assistants already know those. Instead, they focus on ABP's unique architecture, module system, and conventions. + +## How Rules Work + +Large language models don't retain memory between completions. Rules provide persistent, reusable context at the prompt level. + +When applied, rule contents are included at the start of the model context. This gives the AI consistent guidance for generating code, interpreting edits, or helping with workflows. + +## Mini Glossary (ABP Terms) + +- **Application service**: Use-case orchestration (ABP’s primary “business API” surface). Usually exposed remotely via Auto API Controllers or explicit controllers. +- **Auto API Controllers**: ABP can auto-generate HTTP endpoints from `IApplicationService` contracts. +- **Client proxy**: Generated client-side code (Angular/JS/C#) to call remote application services. +- **Integration service (microservices)**: Application-service-like contract intended for **service-to-service** communication; typically exposed separately and consumed via generated C# proxies. +- **Domain vs Application**: Domain holds business rules/invariants; Application coordinates domain + infrastructure and returns DTOs. + +## File Structure + +``` +ai-rules/ +├── README.md +├── common/ # Rules for all ABP projects +│ ├── abp-core.mdc # Core ABP conventions (alwaysApply: true) +│ ├── ddd-patterns.mdc # DDD patterns (Entity, AggregateRoot, Repository) +│ ├── application-layer.mdc # Application services, DTOs, validation +│ ├── authorization.mdc # Permissions and authorization +│ ├── multi-tenancy.mdc # Multi-tenant entities and data isolation +│ ├── infrastructure.mdc # Settings, Features, Caching, Events, Jobs +│ ├── dependency-rules.mdc # Layer dependencies and guardrails +│ ├── development-flow.mdc # Development workflow +│ └── cli-commands.mdc # ABP CLI commands reference +├── ui/ # UI-specific rules (applied by globs) +│ ├── blazor.mdc # Blazor UI patterns +│ ├── angular.mdc # Angular UI patterns +│ └── mvc.mdc # MVC/Razor Pages patterns +├── data/ # Data layer rules (applied by globs) +│ ├── ef-core.mdc # Entity Framework Core patterns +│ └── mongodb.mdc # MongoDB patterns +├── testing/ # Testing rules +│ └── patterns.mdc # Unit and integration test patterns +└── template-specific/ # Template-specific rules + ├── app-nolayers.mdc # Single-layer app template + ├── module.mdc # Module template + └── microservice.mdc # Microservice template +``` + +### Rule Format + +Each rule is a markdown file with frontmatter metadata: + +```markdown +--- +description: "Describes when this rule should apply - used by AI to decide relevance" +globs: "src/**/*.cs" +alwaysApply: false +--- + +# Rule Title + +Your rule content here... +``` + +### Frontmatter Properties + +| Property | Description | +|----------|-------------| +| `description` | Brief description of what the rule covers. Used by AI to determine relevance. | +| `globs` | File patterns that trigger this rule (e.g., `**/*.cs`, `*.Domain/**`). | +| `alwaysApply` | If `true`, rule is always included. If `false`, AI decides based on context. | + +### Rule Types + +| Type | When Applied | +|------|--------------| +| **Always Apply** | Every chat session (`alwaysApply: true`) | +| **Apply Intelligently** | When AI decides it's relevant based on `description` | +| **Apply to Specific Files** | When file matches `globs` pattern | +| **Apply Manually** | When @-mentioned in chat (e.g., `@my-rule`) | + +## Rule Categories + +### Common Rules +Core ABP patterns that apply to all DDD-based templates (app, module, microservice): +- `abp-core.mdc` - Always applied, covers module system, DI conventions, base classes +- `ddd-patterns.mdc` - Entity, AggregateRoot, Repository, Domain Services +- `application-layer.mdc` - Application services, DTOs, validation, error handling +- `authorization.mdc` - Permission system and authorization +- `infrastructure.mdc` - Settings, Features, Caching, Events, Background Jobs +- `dependency-rules.mdc` - Layer dependencies and project structure +- `development-flow.mdc` - Development workflow for adding features + +### UI Rules (Applied by Globs) +- `blazor.mdc` - Applied to `**/*.razor`, `**/Blazor/**/*.cs` +- `angular.mdc` - Applied to `**/angular/**/*.ts` +- `mvc.mdc` - Applied to `**/*.cshtml`, `**/Pages/**/*.cs` + +### Data Rules (Applied by Globs) +- `ef-core.mdc` - Applied to `**/*.EntityFrameworkCore/**/*.cs` +- `mongodb.mdc` - Applied to `**/*.MongoDB/**/*.cs` + +### Template-Specific Rules +- `app-nolayers.mdc` - For single-layer web application template +- `module.mdc` - For reusable module template +- `microservice.mdc` - For microservice template + +## Best Practices + +Good rules are focused, actionable, and scoped: + +- **Keep rules under 500 lines** - Split large rules into multiple, composable rules +- **Provide concrete examples** - Reference actual files or include code snippets +- **Be specific, not vague** - Write rules like clear internal documentation +- **Reference files instead of copying** - This keeps rules short and prevents staleness +- **Start simple** - Add rules only when you notice AI making the same mistake repeatedly + +## What to Avoid + +- **Copying entire style guides**: Use a linter instead. AI already knows common style conventions. +- **Documenting every possible command**: AI knows common tools like `dotnet` and `npm`. +- **Adding instructions for edge cases that rarely apply**: Keep rules focused on patterns you use frequently. +- **Duplicating what's already in your codebase**: Point to canonical examples instead of copying code. +- **Including non-ABP patterns**: Don't add generic .NET/ASP.NET Core guidance—focus on ABP-specific conventions. + +## Contributing + +We welcome community contributions to improve these rules! You can open a PR to add new rules or improve existing ones. + +Please review our [Contribution Guide](../CONTRIBUTING.md) and [Code of Conduct](../CODE_OF_CONDUCT.md) before contributing. + +### Contribution Guidelines + +- Each rule should focus on a single ABP concept or pattern +- Use clear, actionable language +- Include examples where helpful +- Test your rules by using them in a real ABP project +- Keep ABP-specific focus—don't add general .NET patterns + +## Related Resources + +- [Cursor Rules Documentation](https://cursor.com/docs/context/rules) +- [ABP Framework Documentation](https://abp.io/docs) diff --git a/ai-rules/common/abp-core.mdc b/ai-rules/common/abp-core.mdc new file mode 100644 index 0000000000..673c4199e6 --- /dev/null +++ b/ai-rules/common/abp-core.mdc @@ -0,0 +1,182 @@ +--- +description: "Core ABP Framework conventions - module system, dependency injection, and base classes" +alwaysApply: true +--- + +# ABP Core Conventions + +> **Documentation**: https://abp.io/docs/latest +> **API Reference**: https://abp.io/docs/api/ + +## Module System +Every ABP application/module has a module class that configures services: + +```csharp +[DependsOn( + typeof(AbpDddDomainModule), + typeof(AbpEntityFrameworkCoreModule) +)] +public class MyAppModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + // Service registration and configuration + } +} +``` + +> **Note**: Middleware configuration (`OnApplicationInitialization`) should only be done in the final host application, not in reusable modules. + +## Dependency Injection Conventions + +### Automatic Registration +ABP automatically registers services implementing marker interfaces: +- `ITransientDependency` → Transient lifetime +- `ISingletonDependency` → Singleton lifetime +- `IScopedDependency` → Scoped lifetime + +Classes inheriting from `ApplicationService`, `DomainService`, `AbpController` are also auto-registered. + +### Repository Usage +You can use the generic `IRepository` for simple CRUD operations. Define custom repository interfaces only when you need custom query methods: + +```csharp +// Simple CRUD - Generic repository is fine +public class BookAppService : ApplicationService +{ + private readonly IRepository _bookRepository; // ✅ OK for simple operations +} + +// Custom queries needed - Define custom interface +public interface IBookRepository : IRepository +{ + Task FindByNameAsync(string name); // Custom query +} + +public class BookAppService : ApplicationService +{ + private readonly IBookRepository _bookRepository; // ✅ Use custom when needed +} +``` + +### Exposing Services +```csharp +[ExposeServices(typeof(IMyService))] +public class MyService : IMyService, ITransientDependency { } +``` + +## Important Base Classes + +| Base Class | Purpose | +|------------|---------| +| `Entity` | Basic entity with ID | +| `AggregateRoot` | DDD aggregate root | +| `DomainService` | Domain business logic | +| `ApplicationService` | Use case orchestration | +| `AbpController` | REST API controller | + +ABP base classes already inject commonly used services as properties. Before injecting a service, check if it's already available: + +| Property | Available In | Description | +|----------|--------------|-------------| +| `GuidGenerator` | All base classes | Generate GUIDs | +| `Clock` | All base classes | Current time (use instead of `DateTime`) | +| `CurrentUser` | All base classes | Authenticated user info | +| `CurrentTenant` | All base classes | Multi-tenancy context | +| `L` (StringLocalizer) | `ApplicationService`, `AbpController` | Localization | +| `AuthorizationService` | `ApplicationService`, `AbpController` | Permission checks | +| `FeatureChecker` | `ApplicationService`, `AbpController` | Feature availability | +| `DataFilter` | All base classes | Data filtering (soft-delete, tenant) | +| `UnitOfWorkManager` | `ApplicationService`, `DomainService` | Unit of work management | +| `LoggerFactory` | All base classes | Create loggers | +| `Logger` | All base classes | Logging (auto-created) | +| `LazyServiceProvider` | All base classes | Lazy service resolution | + +**Useful methods from base classes:** +- `CheckPolicyAsync()` - Check permission and throw if not granted +- `IsGrantedAsync()` - Check permission without throwing + +## Async Best Practices +- Use async all the way - never use `.Result` or `.Wait()` +- All async methods should end with `Async` suffix +- ABP automatically handles `CancellationToken` in most cases (e.g., from `HttpContext.RequestAborted`) +- Only pass `CancellationToken` explicitly when implementing custom cancellation logic + +## Time Handling +Never use `DateTime.Now` or `DateTime.UtcNow` directly. Use ABP's `IClock` service: + +```csharp +// In classes inheriting from base classes (ApplicationService, DomainService, etc.) +public class BookAppService : ApplicationService +{ + public void DoSomething() + { + var now = Clock.Now; // ✅ Already available as property + } +} + +// In other services - inject IClock +public class MyService : ITransientDependency +{ + private readonly IClock _clock; + + public MyService(IClock clock) => _clock = clock; + + public void DoSomething() + { + var now = _clock.Now; // ✅ Correct + // var now = DateTime.Now; // ❌ Wrong - not testable, ignores timezone settings + } +} +``` + +> **Tip**: Before injecting a service, check if it's already available as a property in your base classes. + +## Business Exceptions +Use `BusinessException` for domain rule violations with namespaced error codes: + +```csharp +throw new BusinessException("MyModule:BookNameAlreadyExists") + .WithData("Name", bookName); +``` + +Configure localization mapping: +```csharp +Configure(options => +{ + options.MapCodeNamespace("MyModule", typeof(MyModuleResource)); +}); +``` + +## Localization +- In base classes (`ApplicationService`, `AbpController`, etc.): Use `L["Key"]` - this is the `IStringLocalizer` property +- In other services: Inject `IStringLocalizer` +- Always localize user-facing messages and exceptions + +**Localization file location**: `*.Domain.Shared/Localization/{ResourceName}/{lang}.json` + +```json +// Example: MyProject.Domain.Shared/Localization/MyProject/en.json +{ + "culture": "en", + "texts": { + "Menu:Home": "Home", + "Welcome": "Welcome", + "BookName": "Book Name" + } +} +``` + +## ❌ Never Use (ABP Anti-Patterns) + +| Don't Use | Use Instead | +|-----------|-------------| +| Minimal APIs | ABP Controllers or Auto API Controllers | +| MediatR | Application Services | +| `DbContext` directly in App Services | `IRepository` | +| `AddScoped/AddTransient/AddSingleton` | `ITransientDependency`, `ISingletonDependency` | +| `DateTime.Now` | `IClock` / `Clock.Now` | +| Custom UnitOfWork | ABP's `IUnitOfWorkManager` | +| Manual HTTP calls from UI | ABP client proxies (`generate-proxy`) | +| Hardcoded role checks | Permission-based authorization | +| Business logic in Controllers | Application Services | diff --git a/ai-rules/common/application-layer.mdc b/ai-rules/common/application-layer.mdc new file mode 100644 index 0000000000..7df134c590 --- /dev/null +++ b/ai-rules/common/application-layer.mdc @@ -0,0 +1,232 @@ +--- +description: "ABP Application Services, DTOs, validation, and error handling patterns" +globs: "**/*.Application/**/*.cs,**/Application/**/*.cs,**/*AppService*.cs,**/*Dto*.cs" +alwaysApply: false +--- + +# ABP Application Layer Patterns + +> **Docs**: https://abp.io/docs/latest/framework/architecture/domain-driven-design/application-services + +## Application Service Structure + +### Interface (Application.Contracts) +```csharp +public interface IBookAppService : IApplicationService +{ + Task GetAsync(Guid id); + Task> GetListAsync(GetBookListInput input); + Task CreateAsync(CreateBookDto input); + Task UpdateAsync(Guid id, UpdateBookDto input); + Task DeleteAsync(Guid id); +} +``` + +### Implementation (Application) +```csharp +public class BookAppService : ApplicationService, IBookAppService +{ + private readonly IBookRepository _bookRepository; + private readonly BookManager _bookManager; + private readonly BookMapper _bookMapper; + + public BookAppService( + IBookRepository bookRepository, + BookManager bookManager, + BookMapper bookMapper) + { + _bookRepository = bookRepository; + _bookManager = bookManager; + _bookMapper = bookMapper; + } + + public async Task GetAsync(Guid id) + { + var book = await _bookRepository.GetAsync(id); + return _bookMapper.MapToDto(book); + } + + [Authorize(BookStorePermissions.Books.Create)] + public async Task CreateAsync(CreateBookDto input) + { + var book = await _bookManager.CreateAsync(input.Name, input.Price); + await _bookRepository.InsertAsync(book); + return _bookMapper.MapToDto(book); + } + + [Authorize(BookStorePermissions.Books.Edit)] + public async Task UpdateAsync(Guid id, UpdateBookDto input) + { + var book = await _bookRepository.GetAsync(id); + await _bookManager.ChangeNameAsync(book, input.Name); + book.SetPrice(input.Price); + await _bookRepository.UpdateAsync(book); + return _bookMapper.MapToDto(book); + } +} +``` + +## Application Service Best Practices +- Don't repeat entity name in method names (`GetAsync` not `GetBookAsync`) +- Accept/return DTOs only, never entities +- ID not inside UpdateDto - pass separately +- Use custom repositories when you need custom queries, generic repository is fine for simple CRUD +- Call `UpdateAsync` explicitly (don't assume change tracking) +- Don't call other app services in same module +- Don't use `IFormFile`/`Stream` - pass `byte[]` from controllers +- Use base class properties (`Clock`, `CurrentUser`, `GuidGenerator`, `L`) instead of injecting these services + +## DTO Naming Conventions + +| Purpose | Convention | Example | +|---------|------------|---------| +| Query input | `Get{Entity}Input` | `GetBookInput` | +| List query input | `Get{Entity}ListInput` | `GetBookListInput` | +| Create input | `Create{Entity}Dto` | `CreateBookDto` | +| Update input | `Update{Entity}Dto` | `UpdateBookDto` | +| Single entity output | `{Entity}Dto` | `BookDto` | +| List item output | `{Entity}ListItemDto` | `BookListItemDto` | + +## DTO Location +- Define DTOs in `*.Application.Contracts` project +- This allows sharing with clients (Blazor, HttpApi.Client) + +## Validation + +### Data Annotations +```csharp +public class CreateBookDto +{ + [Required] + [StringLength(100, MinimumLength = 3)] + public string Name { get; set; } + + [Range(0, 999.99)] + public decimal Price { get; set; } +} +``` + +### Custom Validation with IValidatableObject +Before adding custom validation, decide if it's a **domain rule** or **application rule**: +- **Domain rule**: Put validation in entity constructor or domain service (enforces business invariants) +- **Application rule**: Use DTO validation (input format, required fields) + +Only use `IValidatableObject` for application-level validation that can't be expressed with data annotations: + +```csharp +public class CreateBookDto : IValidatableObject +{ + public string Name { get; set; } + public string Description { get; set; } + + public IEnumerable Validate(ValidationContext validationContext) + { + if (Name == Description) + { + yield return new ValidationResult( + "Name and Description cannot be the same!", + new[] { nameof(Name), nameof(Description) } + ); + } + } +} +``` + +### FluentValidation +```csharp +public class CreateBookDtoValidator : AbstractValidator +{ + public CreateBookDtoValidator() + { + RuleFor(x => x.Name).NotEmpty().Length(3, 100); + RuleFor(x => x.Price).GreaterThan(0); + } +} +``` + +## Error Handling + +### Business Exceptions +```csharp +throw new BusinessException("BookStore:010001") + .WithData("BookName", name); +``` + +### Entity Not Found +```csharp +var book = await _bookRepository.FindAsync(id); +if (book == null) +{ + throw new EntityNotFoundException(typeof(Book), id); +} +``` + +### User-Friendly Exceptions +```csharp +throw new UserFriendlyException(L["BookNotAvailable"]); +``` + +### HTTP Status Code Mapping +Status code mapping is **configurable** in ABP (do not rely on a fixed mapping in business logic). + +| Exception | Typical HTTP Status | +|-----------|-------------| +| `AbpValidationException` | 400 | +| `AbpAuthorizationException` | 401/403 | +| `EntityNotFoundException` | 404 | +| `BusinessException` | 403 (but configurable) | +| Other exceptions | 500 | + +## Auto API Controllers +ABP automatically generates API controllers for application services: +- Interface must inherit `IApplicationService` (which already has `[RemoteService]` attribute) +- HTTP methods determined by method name prefix (Get, Create, Update, Delete) +- Use `[RemoteService(false)]` to disable auto API generation for specific methods + +## Object Mapping (Mapperly / AutoMapper) +ABP supports **both Mapperly and AutoMapper** integrations. But the default mapping library is Mapperly. You need to first check the project's active mapping library. +- Prefer the mapping provider already used in the solution (check existing mapping files / loaded modules). +- In mixed solutions, explicitly setting the default provider may be required (see `docs/en/release-info/migration-guides/AutoMapper-To-Mapperly.md`). + +### Mapperly (compile-time) +Define mappers as partial classes: + +```csharp +[Mapper] +public partial class BookMapper +{ + public partial BookDto MapToDto(Book book); + public partial List MapToDtoList(List books); +} +``` + +Register in module: +```csharp +public override void ConfigureServices(ServiceConfigurationContext context) +{ + context.Services.AddSingleton(); +} +``` + +Usage in application service: +```csharp +public class BookAppService : ApplicationService +{ + private readonly BookMapper _bookMapper; + + public BookAppService(BookMapper bookMapper) + { + _bookMapper = bookMapper; + } + + public BookDto GetBook(Book book) + { + return _bookMapper.MapToDto(book); + } +} +``` + +> **Note**: Mapperly generates mapping code at compile-time, providing better performance than runtime mappers. + +### AutoMapper (runtime) +If the solution uses AutoMapper, mappings are typically defined in `Profile` classes and registered via ABP's AutoMapper integration. diff --git a/ai-rules/common/authorization.mdc b/ai-rules/common/authorization.mdc new file mode 100644 index 0000000000..b7885271c1 --- /dev/null +++ b/ai-rules/common/authorization.mdc @@ -0,0 +1,183 @@ +--- +description: "ABP permission system and authorization patterns" +globs: "**/*Permission*.cs,**/*AppService*.cs,**/*Controller*.cs" +alwaysApply: false +--- + +# ABP Authorization + +> **Docs**: https://abp.io/docs/latest/framework/fundamentals/authorization + +## Permission Definition +Define permissions in `*.Application.Contracts` project: + +```csharp +public static class BookStorePermissions +{ + public const string GroupName = "BookStore"; + + public static class Books + { + public const string Default = GroupName + ".Books"; + public const string Create = Default + ".Create"; + public const string Edit = Default + ".Edit"; + public const string Delete = Default + ".Delete"; + } +} +``` + +Register in provider: +```csharp +public class BookStorePermissionDefinitionProvider : PermissionDefinitionProvider +{ + public override void Define(IPermissionDefinitionContext context) + { + var bookStoreGroup = context.AddGroup(BookStorePermissions.GroupName, L("Permission:BookStore")); + + var booksPermission = bookStoreGroup.AddPermission( + BookStorePermissions.Books.Default, + L("Permission:Books")); + + booksPermission.AddChild( + BookStorePermissions.Books.Create, + L("Permission:Books.Create")); + + booksPermission.AddChild( + BookStorePermissions.Books.Edit, + L("Permission:Books.Edit")); + + booksPermission.AddChild( + BookStorePermissions.Books.Delete, + L("Permission:Books.Delete")); + } + + private static LocalizableString L(string name) + { + return LocalizableString.Create(name); + } +} +``` + +## Using Permissions + +### Declarative (Attribute) +```csharp +[Authorize(BookStorePermissions.Books.Create)] +public virtual async Task CreateAsync(CreateBookDto input) +{ + // Only users with Books.Create permission can execute +} +``` + +### Programmatic Check +```csharp +public class BookAppService : ApplicationService +{ + public async Task DoSomethingAsync() + { + // Check and throw if not granted + await CheckPolicyAsync(BookStorePermissions.Books.Edit); + + // Or check without throwing + if (await IsGrantedAsync(BookStorePermissions.Books.Delete)) + { + // Has permission + } + } +} +``` + +### Allow Anonymous Access +```csharp +[AllowAnonymous] +public virtual async Task GetPublicBookAsync(Guid id) +{ + // No authentication required +} +``` + +## Current User +Access authenticated user info via `CurrentUser` property (available in base classes like `ApplicationService`, `DomainService`, `AbpController`): + +```csharp +public class BookAppService : ApplicationService +{ + public async Task DoSomethingAsync() + { + // CurrentUser is available from base class - no injection needed + var userId = CurrentUser.Id; + var userName = CurrentUser.UserName; + var email = CurrentUser.Email; + var isAuthenticated = CurrentUser.IsAuthenticated; + var roles = CurrentUser.Roles; + var tenantId = CurrentUser.TenantId; + } +} + +// In other services, inject ICurrentUser +public class MyService : ITransientDependency +{ + private readonly ICurrentUser _currentUser; + public MyService(ICurrentUser currentUser) => _currentUser = currentUser; +} +``` + +### Ownership Validation +```csharp +public async Task UpdateMyBookAsync(Guid bookId, UpdateBookDto input) +{ + var book = await _bookRepository.GetAsync(bookId); + + if (book.CreatorId != CurrentUser.Id) + { + throw new AbpAuthorizationException(); + } + + // Update book... +} +``` + +## Multi-Tenancy Permissions +Control permission availability per tenant side: + +```csharp +bookStoreGroup.AddPermission( + BookStorePermissions.Books.Default, + L("Permission:Books"), + multiTenancySide: MultiTenancySides.Tenant // Only for tenants +); +``` + +Options: `MultiTenancySides.Host`, `Tenant`, or `Both` + +## Feature-Dependent Permissions +```csharp +booksPermission.RequireFeatures("BookStore.PremiumFeature"); +``` + +## Permission Management +Grant/revoke permissions programmatically: + +```csharp +public class MyService : ITransientDependency +{ + private readonly IPermissionManager _permissionManager; + + public async Task GrantPermissionToUserAsync(Guid userId, string permissionName) + { + await _permissionManager.SetForUserAsync(userId, permissionName, true); + } + + public async Task GrantPermissionToRoleAsync(string roleName, string permissionName) + { + await _permissionManager.SetForRoleAsync(roleName, permissionName, true); + } +} +``` + +## Security Best Practices +- Never trust client input for user identity +- Use `CurrentUser` property (from base class) or inject `ICurrentUser` +- Validate ownership in application service methods +- Filter queries by current user when appropriate +- Don't expose sensitive fields in DTOs diff --git a/ai-rules/common/cli-commands.mdc b/ai-rules/common/cli-commands.mdc new file mode 100644 index 0000000000..3e406898a1 --- /dev/null +++ b/ai-rules/common/cli-commands.mdc @@ -0,0 +1,90 @@ +--- +description: "ABP CLI commands: generate-proxy, install-libs, add-package-ref, new-module, install-module, update, clean, suite generate (CRUD pages)" +globs: "**/*.csproj,**/appsettings*.json" +alwaysApply: false +--- + +# ABP CLI Commands + +> **Full documentation**: https://abp.io/docs/latest/cli +> Use `abp help [command]` for detailed options. + +## Generate Client Proxies + +```bash +# URL flag: `-u` (short) or `--url` (long). Use whichever your team prefers, but keep it consistent. +# +# Angular (host must be running) +abp generate-proxy -t ng + +# C# client proxies +abp generate-proxy -t csharp -u https://localhost:44300 + +# Integration services only (microservices) +abp generate-proxy -t csharp -u https://localhost:44300 -st integration + +# JavaScript +abp generate-proxy -t js -u https://localhost:44300 +``` + +## Install Client-Side Libraries + +```bash +# Install NPM packages for MVC/Blazor Server +abp install-libs +``` + +## Add Package Reference + +```bash +# Add project reference with module dependency +abp add-package-ref Acme.BookStore.Domain +abp add-package-ref Acme.BookStore.Domain -t Acme.BookStore.Application +``` + +## Module Operations + +```bash +# Create new module in solution +abp new-module Acme.OrderManagement -t module:ddd + +# Install published module +abp install-module Volo.Blogging + +# Add ABP NuGet package +abp add-package Volo.Abp.Caching.StackExchangeRedis +``` + +## Update & Clean + +```bash +abp update # Update all ABP packages +abp update --version 8.0.0 # Specific version +abp clean # Delete bin/obj folders +``` + +## ABP Suite (CRUD Generation) + +Generate CRUD pages from entity JSON (created via Suite UI): + +```bash +abp suite generate --entity .suite/entities/Book.json --solution ./Acme.BookStore.sln +``` + +> **Note**: Entity JSON files are created when you generate an entity via ABP Suite UI. They are stored in `.suite/entities/` folder. +> **Suite docs**: https://abp.io/docs/latest/suite + +## Quick Reference + +| Task | Command | +|------|---------| +| Angular proxies | `abp generate-proxy -t ng` | +| C# proxies | `abp generate-proxy -t csharp -u URL` | +| Install JS libs | `abp install-libs` | +| Add reference | `abp add-package-ref PackageName` | +| Create module | `abp new-module ModuleName` | +| Install module | `abp install-module ModuleName` | +| Update packages | `abp update` | +| Clean solution | `abp clean` | +| Suite CRUD | `abp suite generate -e entity.json -s solution.sln` | +| Get help | `abp help [command]` | diff --git a/ai-rules/common/ddd-patterns.mdc b/ai-rules/common/ddd-patterns.mdc new file mode 100644 index 0000000000..7eccbdbcbe --- /dev/null +++ b/ai-rules/common/ddd-patterns.mdc @@ -0,0 +1,241 @@ +--- +description: "ABP DDD patterns - Entities, Aggregate Roots, Repositories, Domain Services" +globs: "**/*.Domain/**/*.cs,**/Domain/**/*.cs,**/Entities/**/*.cs" +alwaysApply: false +--- + +# ABP DDD Patterns + +> **Docs**: https://abp.io/docs/latest/framework/architecture/domain-driven-design + +## Rich Domain Model vs Anemic Domain Model + +ABP promotes **Rich Domain Model** pattern where entities contain both data AND behavior: + +| Anemic (Anti-pattern) | Rich (Recommended) | +|----------------------|-------------------| +| Entity = data only | Entity = data + behavior | +| Logic in services | Logic in entity methods | +| Public setters | Private setters with methods | +| No validation in entity | Entity enforces invariants | + +**Encapsulation is key**: Protect entity state by using private setters and exposing behavior through methods. + +## Entities + +### Entity Example (Rich Model) +```csharp +public class OrderLine : Entity +{ + public Guid ProductId { get; private set; } + public int Count { get; private set; } + public decimal Price { get; private set; } + + protected OrderLine() { } // For ORM + + internal OrderLine(Guid id, Guid productId, int count, decimal price) : base(id) + { + ProductId = productId; + SetCount(count); // Validates through method + Price = price; + } + + public void SetCount(int count) + { + if (count <= 0) + throw new BusinessException("Orders:InvalidCount"); + Count = count; + } +} +``` + +## Aggregate Roots + +Aggregate roots are consistency boundaries that: +- Own their child entities +- Enforce business rules +- Publish domain events + +```csharp +public class Order : AggregateRoot +{ + public string OrderNumber { get; private set; } + public Guid CustomerId { get; private set; } + public OrderStatus Status { get; private set; } + public ICollection Lines { get; private set; } + + protected Order() { } // For ORM + + public Order(Guid id, string orderNumber, Guid customerId) : base(id) + { + OrderNumber = Check.NotNullOrWhiteSpace(orderNumber, nameof(orderNumber)); + CustomerId = customerId; + Status = OrderStatus.Created; + Lines = new List(); + } + + public void AddLine(Guid lineId, Guid productId, int count, decimal price) + { + // Business rule: Can only add lines to created orders + if (Status != OrderStatus.Created) + throw new BusinessException("Orders:CannotModifyOrder"); + + Lines.Add(new OrderLine(lineId, productId, count, price)); + } + + public void Complete() + { + if (Status != OrderStatus.Created) + throw new BusinessException("Orders:CannotCompleteOrder"); + + Status = OrderStatus.Completed; + + // Publish events for side effects + AddLocalEvent(new OrderCompletedEvent(Id)); // Same transaction + AddDistributedEvent(new OrderCompletedEto { OrderId = Id }); // Cross-service + } +} +``` + +### Domain Events +- `AddLocalEvent()` - Handled within same transaction, can access full entity +- `AddDistributedEvent()` - Handled asynchronously, use ETOs (Event Transfer Objects) + +### Entity Best Practices +- **Encapsulation**: Private setters, public methods that enforce rules +- **Primary constructor**: Enforce invariants, accept `id` parameter +- **Protected parameterless constructor**: Required for ORM +- **Initialize collections**: In primary constructor +- **Virtual members**: For ORM proxy compatibility +- **Reference by Id**: Don't add navigation properties to other aggregates +- **Don't generate GUID in constructor**: Use `IGuidGenerator` externally + +## Repository Pattern + +### When to Use Custom Repository +- **Generic repository** (`IRepository`): Sufficient for simple CRUD operations +- **Custom repository**: Only when you need custom query methods + +### Interface (Domain Layer) +```csharp +// Define custom interface only when custom queries are needed +public interface IOrderRepository : IRepository +{ + Task FindByOrderNumberAsync(string orderNumber, bool includeDetails = false); + Task> GetListByCustomerAsync(Guid customerId, bool includeDetails = false); +} +``` + +### Repository Best Practices +- **One repository per aggregate root only** - Never create repositories for child entities +- Child entities must be accessed/modified only through their aggregate root +- Creating repositories for child entities breaks data consistency (bypasses aggregate root's business rules) +- In ABP, use `AddDefaultRepositories()` without `includeAllEntities: true` to enforce this +- Define custom repository only when custom queries are needed +- ABP handles `CancellationToken` automatically; add parameter only for explicit cancellation control +- Single entity methods: `includeDetails = true` by default +- List methods: `includeDetails = false` by default +- Don't return projection classes +- Interface in Domain, implementation in data layer + +```csharp +// ✅ Correct: Repository for aggregate root (Order) +public interface IOrderRepository : IRepository { } + +// ❌ Wrong: Repository for child entity (OrderLine) +// OrderLine should only be accessed through Order aggregate +public interface IOrderLineRepository : IRepository { } // Don't do this! +``` + +## Domain Services + +Use domain services for business logic that: +- Spans multiple aggregates +- Requires repository queries to enforce rules + +```csharp +public class OrderManager : DomainService +{ + private readonly IOrderRepository _orderRepository; + private readonly IProductRepository _productRepository; + + public OrderManager( + IOrderRepository orderRepository, + IProductRepository productRepository) + { + _orderRepository = orderRepository; + _productRepository = productRepository; + } + + public async Task CreateAsync(string orderNumber, Guid customerId) + { + // Business rule: Order number must be unique + var existing = await _orderRepository.FindByOrderNumberAsync(orderNumber); + if (existing != null) + { + throw new BusinessException("Orders:OrderNumberAlreadyExists") + .WithData("OrderNumber", orderNumber); + } + + return new Order(GuidGenerator.Create(), orderNumber, customerId); + } + + public async Task AddProductAsync(Order order, Guid productId, int count) + { + var product = await _productRepository.GetAsync(productId); + order.AddLine(productId, count, product.Price); + } +} +``` + +### Domain Service Best Practices +- Use `*Manager` suffix naming +- No interface by default (create only if needed) +- Accept/return domain objects, not DTOs +- Don't depend on authenticated user - pass values from application layer +- Use base class properties (`GuidGenerator`, `Clock`) instead of injecting these services + +## Domain Events + +### Local Events +```csharp +// In aggregate +AddLocalEvent(new OrderCompletedEvent(Id)); + +// Handler +public class OrderCompletedEventHandler : ILocalEventHandler, ITransientDependency +{ + public async Task HandleEventAsync(OrderCompletedEvent eventData) + { + // Handle within same transaction + } +} +``` + +### Distributed Events (ETO) +For inter-module/microservice communication: +```csharp +// In Domain.Shared +[EventName("Orders.OrderCompleted")] +public class OrderCompletedEto +{ + public Guid OrderId { get; set; } + public string OrderNumber { get; set; } +} +``` + +## Specifications + +Reusable query conditions: +```csharp +public class CompletedOrdersSpec : Specification +{ + public override Expression> ToExpression() + { + return o => o.Status == OrderStatus.Completed; + } +} + +// Usage +var orders = await _orderRepository.GetListAsync(new CompletedOrdersSpec()); +``` diff --git a/ai-rules/common/dependency-rules.mdc b/ai-rules/common/dependency-rules.mdc new file mode 100644 index 0000000000..3210b7436a --- /dev/null +++ b/ai-rules/common/dependency-rules.mdc @@ -0,0 +1,151 @@ +--- +description: "ABP layer dependency rules and project structure guardrails" +globs: "**/*.csproj,**/*Module*.cs" +alwaysApply: false +--- + +# ABP Dependency Rules + +## Core Principles (All Templates) + +These principles apply regardless of solution structure: + +1. **Domain logic never depends on infrastructure** (no DbContext in domain/application) +2. **Use abstractions** (interfaces) for dependencies +3. **Higher layers depend on lower layers**, never the reverse +4. **Data access through repositories**, not direct DbContext + +## Layered Template Structure + +> **Note**: This section applies to layered templates (app, module). Single-layer and microservice templates have different structures. + +``` +Domain.Shared → Constants, enums, localization keys + ↑ + Domain → Entities, repository interfaces, domain services + ↑ +Application.Contracts → App service interfaces, DTOs + ↑ + Application → App service implementations + ↑ + HttpApi → REST controllers (optional) + ↑ + Host → Final application with DI and middleware +``` + +### Layered Dependency Direction + +| Project | Can Reference | Referenced By | +|---------|---------------|---------------| +| Domain.Shared | Nothing | All | +| Domain | Domain.Shared | Application, Data layer | +| Application.Contracts | Domain.Shared | Application, HttpApi, Clients | +| Application | Domain, Contracts | Host | +| EntityFrameworkCore/MongoDB | Domain | Host only | +| HttpApi | Contracts only | Host | + +## Critical Rules + +### ❌ Never Do +```csharp +// Application layer accessing DbContext directly +public class BookAppService : ApplicationService +{ + private readonly MyDbContext _dbContext; // ❌ WRONG +} + +// Domain depending on application layer +public class BookManager : DomainService +{ + private readonly IBookAppService _appService; // ❌ WRONG +} + +// HttpApi depending on Application implementation +public class BookController : AbpController +{ + private readonly BookAppService _bookAppService; // ❌ WRONG - Use interface +} +``` + +### ✅ Always Do +```csharp +// Application layer using repository abstraction +public class BookAppService : ApplicationService +{ + private readonly IBookRepository _bookRepository; // ✅ CORRECT +} + +// Domain service using domain abstractions +public class BookManager : DomainService +{ + private readonly IBookRepository _bookRepository; // ✅ CORRECT +} + +// HttpApi depending on contracts only +public class BookController : AbpController +{ + private readonly IBookAppService _bookAppService; // ✅ CORRECT +} +``` + +## Repository Pattern Enforcement + +### Interface Location +```csharp +// In Domain project +public interface IBookRepository : IRepository +{ + Task FindByNameAsync(string name); +} +``` + +### Implementation Location +```csharp +// In EntityFrameworkCore project +public class BookRepository : EfCoreRepository, IBookRepository +{ + // Implementation +} + +// In MongoDB project +public class BookRepository : MongoDbRepository, IBookRepository +{ + // Implementation +} +``` + +## Multi-Application Scenarios + +When you have multiple applications (e.g., Admin + Public API): + +### Vertical Separation +``` +MyProject.Admin.Application - Admin-specific services +MyProject.Public.Application - Public-specific services +MyProject.Domain - Shared domain (both reference this) +``` + +### Rules +- Admin and Public application layers **MUST NOT** reference each other +- Share domain logic, not application logic +- Each vertical can have its own DTOs even if similar + +## Enforcement Checklist (Layered Templates) + +When adding a new feature: +1. **Entity changes?** → Domain project +2. **Constants/enums?** → Domain.Shared project +3. **Repository interface?** → Domain project (only if custom queries needed) +4. **Repository implementation?** → EntityFrameworkCore/MongoDB project +5. **DTOs and service interface?** → Application.Contracts project +6. **Service implementation?** → Application project +7. **API endpoint?** → HttpApi project (if not using auto API controllers) + +## Common Violations to Watch + +| Violation | Impact | Fix | +|-----------|--------|-----| +| DbContext in Application | Breaks DB independence | Use repository | +| Entity in DTO | Exposes internals | Map to DTO | +| IQueryable in interface | Breaks abstraction | Return concrete types | +| Cross-module app service call | Tight coupling | Use events or domain | diff --git a/ai-rules/common/development-flow.mdc b/ai-rules/common/development-flow.mdc new file mode 100644 index 0000000000..22ee69687a --- /dev/null +++ b/ai-rules/common/development-flow.mdc @@ -0,0 +1,291 @@ +--- +description: "ABP development workflow - adding features, entities, and migrations" +globs: "**/*AppService*.cs,**/*Application*/**/*.cs,**/*Application.Contracts*/**/*.cs,**/*Dto*.cs,**/*DbContext*.cs,**/*.EntityFrameworkCore/**/*.cs,**/*.MongoDB/**/*.cs,**/*Permission*.cs" +alwaysApply: false +--- + +# ABP Development Workflow + +> **Tutorials**: https://abp.io/docs/latest/tutorials + +## Adding a New Entity (Full Flow) + +### 1. Domain Layer +Create entity (location varies by template: `*.Domain/Entities/` for layered, `Entities/` for single-layer/microservice): + +```csharp +public class Book : AggregateRoot +{ + public string Name { get; private set; } + public decimal Price { get; private set; } + public Guid AuthorId { get; private set; } + + protected Book() { } + + public Book(Guid id, string name, decimal price, Guid authorId) : base(id) + { + Name = Check.NotNullOrWhiteSpace(name, nameof(name)); + SetPrice(price); + AuthorId = authorId; + } + + public void SetPrice(decimal price) + { + Price = Check.Range(price, nameof(price), 0, 9999); + } +} +``` + +### 2. Domain.Shared +Add constants and enums in `*.Domain.Shared/`: + +```csharp +public static class BookConsts +{ + public const int MaxNameLength = 128; +} + +public enum BookType +{ + Novel, + Science, + Biography +} +``` + +### 3. Repository Interface (Optional) +Define custom repository in `*.Domain/` only if you need custom query methods. For simple CRUD, use generic `IRepository` directly: + +```csharp +// Only if custom queries are needed +public interface IBookRepository : IRepository +{ + Task FindByNameAsync(string name); +} +``` + +### 4. EF Core Configuration +In `*.EntityFrameworkCore/`: + +**DbContext:** +```csharp +public DbSet Books { get; set; } +``` + +**OnModelCreating:** +```csharp +builder.Entity(b => +{ + b.ToTable(MyProjectConsts.DbTablePrefix + "Books", MyProjectConsts.DbSchema); + b.ConfigureByConvention(); + b.Property(x => x.Name).IsRequired().HasMaxLength(BookConsts.MaxNameLength); + b.HasIndex(x => x.Name); +}); +``` + +**Repository Implementation (only if custom interface defined):** +```csharp +public class BookRepository : EfCoreRepository, IBookRepository +{ + public BookRepository(IDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + } + + public async Task FindByNameAsync(string name) + { + return await (await GetDbSetAsync()) + .FirstOrDefaultAsync(b => b.Name == name); + } +} +``` + +### 5. Run Migration +```bash +cd src/MyProject.EntityFrameworkCore + +# Add migration +dotnet ef migrations add Added_Book + +# Apply migration (choose one): +dotnet run --project ../MyProject.DbMigrator # Recommended - also seeds data +# OR +dotnet ef database update # EF Core command only +``` + +### 6. Application.Contracts +Create DTOs and service interface: + +```csharp +// DTOs +public class BookDto : EntityDto +{ + public string Name { get; set; } + public decimal Price { get; set; } + public Guid AuthorId { get; set; } +} + +public class CreateBookDto +{ + [Required] + [StringLength(BookConsts.MaxNameLength)] + public string Name { get; set; } + + [Range(0, 9999)] + public decimal Price { get; set; } + + [Required] + public Guid AuthorId { get; set; } +} + +// Service Interface +public interface IBookAppService : IApplicationService +{ + Task GetAsync(Guid id); + Task> GetListAsync(PagedAndSortedResultRequestDto input); + Task CreateAsync(CreateBookDto input); +} +``` + +### 7. Object Mapping (Mapperly / AutoMapper) +ABP supports both Mapperly and AutoMapper. Prefer the provider already used in the solution. + +If the solution uses **Mapperly**, create a mapper in the Application project: + +```csharp +[Mapper] +public partial class BookMapper +{ + public partial BookDto MapToDto(Book book); + public partial List MapToDtoList(List books); +} +``` + +Register in module: +```csharp +context.Services.AddSingleton(); +``` + +### 8. Application Service +Implement service (using generic repository - use `IBookRepository` if you defined custom interface in step 3): + +```csharp +public class BookAppService : ApplicationService, IBookAppService +{ + private readonly IRepository _bookRepository; // Or IBookRepository + private readonly BookMapper _bookMapper; + + public BookAppService( + IRepository bookRepository, + BookMapper bookMapper) + { + _bookRepository = bookRepository; + _bookMapper = bookMapper; + } + + public async Task GetAsync(Guid id) + { + var book = await _bookRepository.GetAsync(id); + return _bookMapper.MapToDto(book); + } + + [Authorize(MyProjectPermissions.Books.Create)] + public async Task CreateAsync(CreateBookDto input) + { + var book = new Book( + GuidGenerator.Create(), + input.Name, + input.Price, + input.AuthorId + ); + + await _bookRepository.InsertAsync(book); + return _bookMapper.MapToDto(book); + } +} +``` + +### 9. Add Localization +In `*.Domain.Shared/Localization/*/en.json`: + +```json +{ + "Book": "Book", + "Books": "Books", + "BookName": "Name", + "BookPrice": "Price" +} +``` + +### 10. Add Permissions (if needed) +```csharp +public static class MyProjectPermissions +{ + public static class Books + { + public const string Default = "MyProject.Books"; + public const string Create = Default + ".Create"; + } +} +``` + +### 11. Add Tests +```csharp +public class BookAppService_Tests : MyProjectApplicationTestBase +{ + private readonly IBookAppService _bookAppService; + + public BookAppService_Tests() + { + _bookAppService = GetRequiredService(); + } + + [Fact] + public async Task Should_Create_Book() + { + var result = await _bookAppService.CreateAsync(new CreateBookDto + { + Name = "Test Book", + Price = 19.99m + }); + + result.Id.ShouldNotBe(Guid.Empty); + result.Name.ShouldBe("Test Book"); + } +} +``` + +## Quick Reference Commands + +### Build Solution +```bash +dotnet build +``` + +### Run Migrations +```bash +cd src/MyProject.EntityFrameworkCore +dotnet ef migrations add MigrationName +dotnet run --project ../MyProject.DbMigrator # Apply migration + seed data +``` + +### Generate Angular Proxies +```bash +abp generate-proxy -t ng +``` + +## Checklist for New Features + +- [ ] Entity created with proper constructors +- [ ] Constants in Domain.Shared +- [ ] Custom repository interface in Domain (only if custom queries needed) +- [ ] EF Core configuration added +- [ ] Custom repository implementation (only if interface defined) +- [ ] Migration generated and applied (use DbMigrator) +- [ ] Mapperly mapper created and registered +- [ ] DTOs created in Application.Contracts +- [ ] Service interface defined +- [ ] Service implementation with authorization +- [ ] Localization keys added +- [ ] Permissions defined (if applicable) +- [ ] Tests written diff --git a/ai-rules/common/infrastructure.mdc b/ai-rules/common/infrastructure.mdc new file mode 100644 index 0000000000..1a2c555581 --- /dev/null +++ b/ai-rules/common/infrastructure.mdc @@ -0,0 +1,244 @@ +--- +description: "ABP infrastructure services - Settings, Features, Caching, Events, Background Jobs" +globs: "**/*Setting*.cs,**/*Feature*.cs,**/*Cache*.cs,**/*Event*.cs,**/*Job*.cs" +alwaysApply: false +--- + +# ABP Infrastructure Services + +> **Docs**: https://abp.io/docs/latest/framework/infrastructure + +## Settings + +### Define Settings +```csharp +public class MySettingDefinitionProvider : SettingDefinitionProvider +{ + public override void Define(ISettingDefinitionContext context) + { + context.Add( + new SettingDefinition("MyApp.MaxItemCount", "10"), + new SettingDefinition("MyApp.EnableFeature", "false"), + new SettingDefinition("MyApp.SecretKey", isEncrypted: true) + ); + } +} +``` + +### Read Settings +```csharp +public class MyService : ITransientDependency +{ + private readonly ISettingProvider _settingProvider; + + public async Task DoSomethingAsync() + { + var maxCount = await _settingProvider.GetAsync("MyApp.MaxItemCount"); + var isEnabled = await _settingProvider.IsTrueAsync("MyApp.EnableFeature"); + } +} +``` + +### Setting Value Providers (Priority Order) +1. User settings (highest) +2. Tenant settings +3. Global settings +4. Configuration (appsettings.json) +5. Default value (lowest) + +## Features + +### Define Features +```csharp +public class MyFeatureDefinitionProvider : FeatureDefinitionProvider +{ + public override void Define(IFeatureDefinitionContext context) + { + var myGroup = context.AddGroup("MyApp"); + + myGroup.AddFeature( + "MyApp.PdfReporting", + defaultValue: "false", + valueType: new ToggleStringValueType() + ); + + myGroup.AddFeature( + "MyApp.MaxProductCount", + defaultValue: "10", + valueType: new FreeTextStringValueType(new NumericValueValidator(1, 1000)) + ); + } +} +``` + +### Check Features +```csharp +[RequiresFeature("MyApp.PdfReporting")] +public async Task GetPdfReportAsync() +{ + // Only executes if feature is enabled +} + +// Or programmatically +if (await _featureChecker.IsEnabledAsync("MyApp.PdfReporting")) +{ + // Feature is enabled for current tenant +} + +var maxCount = await _featureChecker.GetAsync("MyApp.MaxProductCount"); +``` + +## Distributed Caching + +### Typed Cache +```csharp +public class BookService : ITransientDependency +{ + private readonly IDistributedCache _cache; + private readonly IClock _clock; + + public BookService(IDistributedCache cache, IClock clock) + { + _cache = cache; + _clock = clock; + } + + public async Task GetAsync(Guid bookId) + { + return await _cache.GetOrAddAsync( + bookId.ToString(), + async () => await GetBookFromDatabaseAsync(bookId), + () => new DistributedCacheEntryOptions + { + AbsoluteExpiration = _clock.Now.AddHours(1) + } + ); + } +} + +[CacheName("Books")] +public class BookCacheItem +{ + public string Name { get; set; } + public decimal Price { get; set; } +} +``` + +## Event Bus + +### Local Events (Same Process) +```csharp +// Event class +public class OrderCreatedEvent +{ + public Order Order { get; set; } +} + +// Handler +public class OrderCreatedEventHandler : ILocalEventHandler, ITransientDependency +{ + public async Task HandleEventAsync(OrderCreatedEvent eventData) + { + // Handle within same transaction + } +} + +// Publish +await _localEventBus.PublishAsync(new OrderCreatedEvent { Order = order }); +``` + +### Distributed Events (Cross-Service) +```csharp +// Event Transfer Object (in Domain.Shared) +[EventName("MyApp.Order.Created")] +public class OrderCreatedEto +{ + public Guid OrderId { get; set; } + public string OrderNumber { get; set; } +} + +// Handler +public class OrderCreatedEtoHandler : IDistributedEventHandler, ITransientDependency +{ + public async Task HandleEventAsync(OrderCreatedEto eventData) + { + // Handle distributed event + } +} + +// Publish +await _distributedEventBus.PublishAsync(new OrderCreatedEto { ... }); +``` + +### When to Use Which +- **Local**: Within same module/bounded context +- **Distributed**: Cross-module or microservice communication + +## Background Jobs + +### Define Job +```csharp +public class EmailSendingArgs +{ + public string EmailAddress { get; set; } + public string Subject { get; set; } + public string Body { get; set; } +} + +public class EmailSendingJob : AsyncBackgroundJob, ITransientDependency +{ + private readonly IEmailSender _emailSender; + + public EmailSendingJob(IEmailSender emailSender) + { + _emailSender = emailSender; + } + + public override async Task ExecuteAsync(EmailSendingArgs args) + { + await _emailSender.SendAsync(args.EmailAddress, args.Subject, args.Body); + } +} +``` + +### Enqueue Job +```csharp +await _backgroundJobManager.EnqueueAsync( + new EmailSendingArgs + { + EmailAddress = "user@example.com", + Subject = "Hello", + Body = "..." + }, + delay: TimeSpan.FromMinutes(5) // Optional delay +); +``` + +## Localization + +### Define Resource +```csharp +[LocalizationResourceName("MyModule")] +public class MyModuleResource { } +``` + +### JSON Structure +```json +{ + "culture": "en", + "texts": { + "HelloWorld": "Hello World!", + "Menu:Books": "Books" + } +} +``` + +### Usage +- In `ApplicationService`: Use `L["Key"]` property (already available from base class) +- In other services: Inject `IStringLocalizer` + +> **Tip**: ABP base classes already provide commonly used services as properties. Check before injecting: +> - `StringLocalizer` (L), `Clock`, `CurrentUser`, `CurrentTenant`, `GuidGenerator` +> - `AuthorizationService`, `FeatureChecker`, `DataFilter` +> - `LoggerFactory`, `Logger` +> - Methods like `CheckPolicyAsync()` for authorization checks diff --git a/ai-rules/common/multi-tenancy.mdc b/ai-rules/common/multi-tenancy.mdc new file mode 100644 index 0000000000..54574a1c31 --- /dev/null +++ b/ai-rules/common/multi-tenancy.mdc @@ -0,0 +1,162 @@ +--- +description: "ABP Multi-Tenancy patterns - tenant-aware entities, data isolation, and tenant switching" +globs: "**/*Tenant*.cs,**/*MultiTenant*.cs,**/Entities/**/*.cs" +alwaysApply: false +--- + +# ABP Multi-Tenancy + +> **Docs**: https://abp.io/docs/latest/framework/architecture/multi-tenancy + +## Making Entities Multi-Tenant + +Implement `IMultiTenant` interface to make entities tenant-aware: + +```csharp +public class Product : AggregateRoot, IMultiTenant +{ + public Guid? TenantId { get; set; } // Required by IMultiTenant + + public string Name { get; private set; } + public decimal Price { get; private set; } + + protected Product() { } + + public Product(Guid id, string name, decimal price) : base(id) + { + Name = name; + Price = price; + // TenantId is automatically set from CurrentTenant.Id + } +} +``` + +**Key points:** +- `TenantId` is **nullable** - `null` means entity belongs to Host +- ABP **automatically filters** queries by current tenant +- ABP **automatically sets** `TenantId` when creating entities + +## Accessing Current Tenant + +Use `CurrentTenant` property (available in base classes) or inject `ICurrentTenant`: + +```csharp +public class ProductAppService : ApplicationService +{ + public async Task DoSomethingAsync() + { + // Available from base class + var tenantId = CurrentTenant.Id; // Guid? - null for host + var tenantName = CurrentTenant.Name; // string? + var isAvailable = CurrentTenant.IsAvailable; // true if Id is not null + } +} + +// In other services +public class MyService : ITransientDependency +{ + private readonly ICurrentTenant _currentTenant; + public MyService(ICurrentTenant currentTenant) => _currentTenant = currentTenant; +} +``` + +## Switching Tenant Context + +Use `CurrentTenant.Change()` to temporarily switch tenant (useful in host context): + +```csharp +public class ProductManager : DomainService +{ + private readonly IRepository _productRepository; + + public async Task GetProductCountAsync(Guid? tenantId) + { + // Switch to specific tenant + using (CurrentTenant.Change(tenantId)) + { + return await _productRepository.GetCountAsync(); + } + // Automatically restored to previous tenant after using block + } + + public async Task DoHostOperationAsync() + { + // Switch to host context + using (CurrentTenant.Change(null)) + { + // Operations here are in host context + } + } +} +``` + +> **Important**: Always use `Change()` with a `using` statement. + +## Disabling Multi-Tenant Filter + +To query all tenants' data (only works with single database): + +```csharp +public class ProductManager : DomainService +{ + public async Task GetAllProductCountAsync() + { + // DataFilter is available from base class + using (DataFilter.Disable()) + { + return await _productRepository.GetCountAsync(); + // Returns count from ALL tenants + } + } +} +``` + +> **Note**: This doesn't work with separate databases per tenant. + +## Database Architecture Options + +| Approach | Description | Use Case | +|----------|-------------|----------| +| Single Database | All tenants share one database | Simple, cost-effective | +| Database per Tenant | Each tenant has dedicated database | Data isolation, compliance | +| Hybrid | Mix of shared and dedicated | Flexible, premium tenants | + +Connection strings are configured per tenant in Tenant Management module. + +## Best Practices + +1. **Always implement `IMultiTenant`** for tenant-specific entities +2. **Never manually filter by `TenantId`** - ABP does it automatically +3. **Don't change `TenantId` after creation** - it moves entity between tenants +4. **Use `Change()` scope carefully** - nested scopes are supported +5. **Test both host and tenant contexts** - ensure proper data isolation +6. **Consider nullable `TenantId`** - entity may be host-only or shared + +## Enabling Multi-Tenancy + +```csharp +Configure(options => +{ + options.IsEnabled = true; // Enabled by default in ABP templates +}); +``` + +Check `MultiTenancyConsts.IsEnabled` in your solution for centralized control. + +## Tenant Resolution + +ABP resolves current tenant from (in order): +1. Current user's claims +2. Query string (`?__tenant=...`) +3. Route (`/{__tenant}/...`) +4. HTTP header (`__tenant`) +5. Cookie (`__tenant`) +6. Domain/subdomain (if configured) + +For subdomain-based resolution: +```csharp +Configure(options => +{ + options.AddDomainTenantResolver("{0}.mydomain.com"); +}); +``` diff --git a/ai-rules/data/ef-core.mdc b/ai-rules/data/ef-core.mdc new file mode 100644 index 0000000000..6ca6a423d2 --- /dev/null +++ b/ai-rules/data/ef-core.mdc @@ -0,0 +1,254 @@ +--- +description: "ABP Entity Framework Core patterns - DbContext, migrations, repositories" +globs: "**/*.EntityFrameworkCore/**/*.cs,**/EntityFrameworkCore/**/*.cs,**/*DbContext*.cs" +alwaysApply: false +--- + +# ABP Entity Framework Core + +> **Docs**: https://abp.io/docs/latest/framework/data/entity-framework-core + +## DbContext Configuration + +```csharp +[ConnectionStringName("Default")] +public class MyProjectDbContext : AbpDbContext +{ + public DbSet Books { get; set; } + public DbSet Authors { get; set; } + + public MyProjectDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + // Configure all entities + builder.ConfigureMyProject(); + } +} +``` + +## Entity Configuration + +```csharp +public static class MyProjectDbContextModelCreatingExtensions +{ + public static void ConfigureMyProject(this ModelBuilder builder) + { + Check.NotNull(builder, nameof(builder)); + + builder.Entity(b => + { + b.ToTable(MyProjectConsts.DbTablePrefix + "Books", MyProjectConsts.DbSchema); + b.ConfigureByConvention(); // ABP conventions (audit, soft-delete, etc.) + + // Property configurations + b.Property(x => x.Name) + .IsRequired() + .HasMaxLength(BookConsts.MaxNameLength); + + b.Property(x => x.Price) + .HasColumnType("decimal(18,2)"); + + // Indexes + b.HasIndex(x => x.Name); + + // Relationships + b.HasOne() + .WithMany() + .HasForeignKey(x => x.AuthorId) + .OnDelete(DeleteBehavior.Restrict); + }); + } +} +``` + +## Repository Implementation + +```csharp +public class BookRepository : EfCoreRepository, IBookRepository +{ + public BookRepository(IDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + } + + public async Task FindByNameAsync( + string name, + bool includeDetails = true, + CancellationToken cancellationToken = default) + { + var dbSet = await GetDbSetAsync(); + + return await dbSet + .IncludeDetails(includeDetails) + .FirstOrDefaultAsync( + b => b.Name == name, + GetCancellationToken(cancellationToken)); + } + + public async Task> GetListByAuthorAsync( + Guid authorId, + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + var dbSet = await GetDbSetAsync(); + + return await dbSet + .IncludeDetails(includeDetails) + .Where(b => b.AuthorId == authorId) + .ToListAsync(GetCancellationToken(cancellationToken)); + } + + public override async Task> WithDetailsAsync() + { + return (await GetQueryableAsync()) + .Include(b => b.Reviews); + } +} +``` + +## Extension Method for Include +```csharp +public static class BookEfCoreQueryableExtensions +{ + public static IQueryable IncludeDetails( + this IQueryable queryable, + bool include = true) + { + if (!include) + { + return queryable; + } + + return queryable + .Include(b => b.Reviews); + } +} +``` + +## Migration Commands + +```bash +# Navigate to EF Core project +cd src/MyProject.EntityFrameworkCore + +# Add migration +dotnet ef migrations add MigrationName + +# Apply migration (choose one): +dotnet run --project ../MyProject.DbMigrator # Recommended - also seeds data +dotnet ef database update # EF Core command only + +# Remove last migration (if not applied) +dotnet ef migrations remove + +# Generate SQL script +dotnet ef migrations script +``` + +> **Note**: ABP templates include `IDesignTimeDbContextFactory` in the EF Core project, so `-s` (startup project) parameter is not needed. + +## Module Configuration + +```csharp +[DependsOn(typeof(AbpEntityFrameworkCoreModule))] +public class MyProjectEntityFrameworkCoreModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddAbpDbContext(options => + { + // Add default repositories for aggregate roots only (DDD best practice) + options.AddDefaultRepositories(); + // ⚠️ Avoid includeAllEntities: true - it creates repositories for child entities, + // allowing them to be modified without going through the aggregate root, + // which breaks data consistency + }); + + Configure(options => + { + options.UseSqlServer(); // or UseNpgsql(), UseMySql(), etc. + }); + } +} +``` + +## Best Practices + +### Repositories for Aggregate Roots Only +Don't use `includeAllEntities: true` in `AddDefaultRepositories()`. This creates repositories for child entities, allowing direct modification without going through the aggregate root - breaking DDD data consistency rules. + +```csharp +// ✅ Correct - Only aggregate roots get repositories +options.AddDefaultRepositories(); + +// ❌ Avoid - Creates repositories for ALL entities including child entities +options.AddDefaultRepositories(includeAllEntities: true); +``` + +### Always Call ConfigureByConvention +```csharp +builder.Entity(b => +{ + b.ConfigureByConvention(); // Don't forget this! + // Other configurations... +}); +``` + +### Use Table Prefix +```csharp +public static class MyProjectConsts +{ + public const string DbTablePrefix = "App"; + public const string DbSchema = null; // Or "myschema" +} +``` + +### Performance Tips +- Add explicit indexes for frequently queried fields +- Use `AsNoTracking()` for read-only queries +- Avoid N+1 queries with `.Include()` or specifications +- ABP handles cancellation automatically; use `GetCancellationToken(cancellationToken)` only in custom repository methods +- Consider query splitting for complex queries with multiple collections + +### Accessing Raw DbContext +```csharp +public async Task CustomOperationAsync() +{ + var dbContext = await GetDbContextAsync(); + + // Raw SQL + await dbContext.Database.ExecuteSqlRawAsync( + "UPDATE Books SET IsPublished = 1 WHERE AuthorId = {0}", + authorId + ); +} +``` + +## Data Seeding + +```csharp +public class MyProjectDataSeedContributor : IDataSeedContributor, ITransientDependency +{ + private readonly IRepository _bookRepository; + private readonly IGuidGenerator _guidGenerator; + + public async Task SeedAsync(DataSeedContext context) + { + if (await _bookRepository.GetCountAsync() > 0) + { + return; + } + + await _bookRepository.InsertAsync( + new Book(_guidGenerator.Create(), "Sample Book", 19.99m, Guid.Empty), + autoSave: true + ); + } +} +``` diff --git a/ai-rules/data/mongodb.mdc b/ai-rules/data/mongodb.mdc new file mode 100644 index 0000000000..671d2052c4 --- /dev/null +++ b/ai-rules/data/mongodb.mdc @@ -0,0 +1,203 @@ +--- +description: "ABP MongoDB patterns - MongoDbContext and repositories" +globs: "**/*.MongoDB/**/*.cs,**/MongoDB/**/*.cs,**/*MongoDb*.cs" +alwaysApply: false +--- + +# ABP MongoDB + +> **Docs**: https://abp.io/docs/latest/framework/data/mongodb + +## MongoDbContext Configuration + +```csharp +[ConnectionStringName("Default")] +public class MyProjectMongoDbContext : AbpMongoDbContext +{ + public IMongoCollection Books => Collection(); + public IMongoCollection Authors => Collection(); + + protected override void CreateModel(IMongoModelBuilder modelBuilder) + { + base.CreateModel(modelBuilder); + + modelBuilder.ConfigureMyProject(); + } +} +``` + +## Entity Configuration + +```csharp +public static class MyProjectMongoDbContextExtensions +{ + public static void ConfigureMyProject(this IMongoModelBuilder builder) + { + Check.NotNull(builder, nameof(builder)); + + builder.Entity(b => + { + b.CollectionName = MyProjectConsts.DbTablePrefix + "Books"; + }); + + builder.Entity(b => + { + b.CollectionName = MyProjectConsts.DbTablePrefix + "Authors"; + }); + } +} +``` + +## Repository Implementation + +```csharp +public class BookRepository : MongoDbRepository, IBookRepository +{ + public BookRepository(IMongoDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + } + + public async Task FindByNameAsync( + string name, + bool includeDetails = true, + CancellationToken cancellationToken = default) + { + return await (await GetQueryableAsync()) + .FirstOrDefaultAsync( + b => b.Name == name, + GetCancellationToken(cancellationToken)); + } + + public async Task> GetListByAuthorAsync( + Guid authorId, + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + return await (await GetQueryableAsync()) + .Where(b => b.AuthorId == authorId) + .ToListAsync(GetCancellationToken(cancellationToken)); + } +} +``` + +## Module Configuration + +```csharp +[DependsOn(typeof(AbpMongoDbModule))] +public class MyProjectMongoDbModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddMongoDbContext(options => + { + // Add default repositories for aggregate roots only (DDD best practice) + options.AddDefaultRepositories(); + // ⚠️ Avoid includeAllEntities: true - breaks DDD data consistency + }); + } +} +``` + +## Connection String + +In `appsettings.json`: +```json +{ + "ConnectionStrings": { + "Default": "mongodb://localhost:27017/MyProjectDb" + } +} +``` + +## Key Differences from EF Core + +### No Migrations +MongoDB is schema-less; no migrations needed. Changes to entity structure are handled automatically. + +### includeDetails Parameter +Often ignored in MongoDB because documents typically embed related data: + +```csharp +public async Task> GetListAsync( + bool includeDetails = false, // Usually ignored + CancellationToken cancellationToken = default) +{ + // MongoDB documents already include nested data + return await (await GetQueryableAsync()) + .ToListAsync(GetCancellationToken(cancellationToken)); +} +``` + +### Embedded Documents vs References +```csharp +// Embedded (stored in same document) +public class Order : AggregateRoot +{ + public List Lines { get; set; } // Embedded +} + +// Reference (separate collection, store ID only) +public class Order : AggregateRoot +{ + public Guid CustomerId { get; set; } // Reference by ID +} +``` + +### No Change Tracking +MongoDB doesn't track entity changes automatically: + +```csharp +public async Task UpdateBookAsync(Guid id, string newName) +{ + var book = await _bookRepository.GetAsync(id); + book.SetName(newName); + + // Must explicitly update + await _bookRepository.UpdateAsync(book); +} +``` + +## Direct Collection Access + +```csharp +public async Task CustomOperationAsync() +{ + var collection = await GetCollectionAsync(); + + // Use MongoDB driver directly + var filter = Builders.Filter.Eq(b => b.AuthorId, authorId); + var update = Builders.Update.Set(b => b.IsPublished, true); + + await collection.UpdateManyAsync(filter, update); +} +``` + +## Indexing + +Configure indexes in repository or via MongoDB driver: + +```csharp +public class BookRepository : MongoDbRepository, IBookRepository +{ + public override async Task> GetQueryableAsync() + { + var collection = await GetCollectionAsync(); + + // Ensure index exists + var indexKeys = Builders.IndexKeys.Ascending(b => b.Name); + await collection.Indexes.CreateOneAsync(new CreateIndexModel(indexKeys)); + + return await base.GetQueryableAsync(); + } +} +``` + +## Best Practices + +- Design documents for query patterns (denormalize when needed) +- Use references for frequently changing data +- Use embedding for data that's always accessed together +- Add indexes for frequently queried fields +- Use `GetCancellationToken(cancellationToken)` for proper cancellation +- Remember: ABP data filters (soft-delete, multi-tenancy) work with MongoDB too diff --git a/ai-rules/template-specific/app-nolayers.mdc b/ai-rules/template-specific/app-nolayers.mdc new file mode 100644 index 0000000000..4d9a00458f --- /dev/null +++ b/ai-rules/template-specific/app-nolayers.mdc @@ -0,0 +1,79 @@ +--- +description: "ABP Single-Layer (No-Layers) application template specific patterns" +globs: "**/src/*/*Module.cs,**/src/*/Entities/**/*.cs,**/src/*/Services/**/*.cs,**/src/*/Data/**/*.cs" +alwaysApply: false +--- + +# ABP Single-Layer Application Template + +> **Docs**: https://abp.io/docs/latest/solution-templates/single-layer-web-application + +## Solution Structure + +Single project containing everything: + +``` +MyProject/ +├── src/ +│ └── MyProject/ +│ ├── Data/ # DbContext, migrations +│ ├── Entities/ # Domain entities +│ ├── Services/ # Application services + DTOs +│ ├── Pages/ # Razor pages / Blazor components +│ └── MyProjectModule.cs +└── test/ + └── MyProject.Tests/ +``` + +## Key Differences from Layered + +| Layered Template | Single-Layer Template | +|------------------|----------------------| +| DTOs in Application.Contracts | DTOs in Services folder (same project) | +| Repository interfaces in Domain | Use generic `IRepository` directly | +| Separate Domain.Shared for constants | Constants in same project | +| Multiple module classes | Single module class | + +## File Organization + +Group related files by feature: + +``` +Services/ +├── Books/ +│ ├── BookAppService.cs +│ ├── BookDto.cs +│ ├── CreateBookDto.cs +│ └── IBookAppService.cs +└── Authors/ + ├── AuthorAppService.cs + └── ... +``` + +## Simplified Entity (Still keep invariants) + +Single-layer templates are structurally simpler, but you may still have real business invariants. + +- For **trivial CRUD** entities, public setters can be acceptable. +- For **non-trivial business rules**, still prefer encapsulation (private setters + methods) to prevent invalid states. + +```csharp +public class Book : AuditedAggregateRoot +{ + public string Name { get; set; } // OK for trivial CRUD only + public decimal Price { get; set; } +} +``` + +## No Custom Repository Needed + +Use generic repository directly - no need to define custom interfaces: + +```csharp +public class BookAppService : ApplicationService +{ + private readonly IRepository _bookRepository; + + // Generic repository is sufficient for single-layer apps +} +``` diff --git a/ai-rules/template-specific/microservice.mdc b/ai-rules/template-specific/microservice.mdc new file mode 100644 index 0000000000..749dfca572 --- /dev/null +++ b/ai-rules/template-specific/microservice.mdc @@ -0,0 +1,209 @@ +--- +description: "ABP Microservice solution template specific patterns" +alwaysApply: false +--- + +# ABP Microservice Solution Template + +> **Docs**: https://abp.io/docs/latest/solution-templates/microservice + +## Solution Structure + +``` +MyMicroservice/ +├── apps/ # UI applications +│ ├── web/ # Web application +│ ├── public-web/ # Public website +│ └── auth-server/ # Authentication server (OpenIddict) +├── gateways/ # BFF pattern - one gateway per UI +│ └── web-gateway/ # YARP reverse proxy +├── services/ # Microservices +│ ├── administration/ # Permissions, settings, features +│ ├── identity/ # Users, roles +│ └── [your-services]/ # Your business services +└── etc/ + ├── docker/ # Docker compose for local infra + └── helm/ # Kubernetes deployment +``` + +## Microservice Structure (NOT Layered!) + +Each microservice has simplified structure - everything in one project: + +``` +services/ordering/ +├── OrderingService/ # Main project +│ ├── Entities/ +│ ├── Services/ +│ ├── IntegrationServices/ # For inter-service communication +│ ├── Data/ # DbContext (implements IHasEventInbox, IHasEventOutbox) +│ └── OrderingServiceModule.cs +├── OrderingService.Contracts/ # Interfaces, DTOs, ETOs (shared) +└── OrderingService.Tests/ +``` + +## Inter-Service Communication + +### 1. Integration Services (Synchronous HTTP) + +For synchronous calls, use **Integration Services** - NOT regular application services. + +#### Step 1: Provider Service - Create Integration Service + +```csharp +// In CatalogService.Contracts project +[IntegrationService] +public interface IProductIntegrationService : IApplicationService +{ + Task> GetProductsByIdsAsync(List ids); +} + +// In CatalogService project +[IntegrationService] +public class ProductIntegrationService : ApplicationService, IProductIntegrationService +{ + public async Task> GetProductsByIdsAsync(List ids) + { + var products = await _productRepository.GetListAsync(p => ids.Contains(p.Id)); + return ObjectMapper.Map, List>(products); + } +} +``` + +#### Step 2: Provider Service - Expose Integration Services + +```csharp +// In CatalogServiceModule.cs +Configure(options => +{ + options.ExposeIntegrationServices = true; +}); +``` + +#### Step 3: Consumer Service - Add Package Reference + +Add reference to provider's Contracts project (via ABP Studio or manually): +- Right-click OrderingService → Add Package Reference → Select `CatalogService.Contracts` + +#### Step 4: Consumer Service - Generate Proxies + +```bash +# Run ABP CLI in consumer service folder +abp generate-proxy -t csharp -u http://localhost:44361 -m catalog --without-contracts +``` + +Or use ABP Studio: Right-click service → ABP CLI → Generate Proxy → C# + +#### Step 5: Consumer Service - Register HTTP Client Proxies + +```csharp +// In OrderingServiceModule.cs +[DependsOn(typeof(CatalogServiceContractsModule))] // Add module dependency +public class OrderingServiceModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + // Register static HTTP client proxies + context.Services.AddStaticHttpClientProxies( + typeof(CatalogServiceContractsModule).Assembly, + "CatalogService"); + } +} +``` + +#### Step 6: Consumer Service - Configure Remote Service URL + +```json +// appsettings.json +"RemoteServices": { + "CatalogService": { + "BaseUrl": "http://localhost:44361" + } +} +``` + +#### Step 7: Use Integration Service + +```csharp +public class OrderAppService : ApplicationService +{ + private readonly IProductIntegrationService _productIntegrationService; + + public async Task> GetListAsync() + { + var orders = await _orderRepository.GetListAsync(); + var productIds = orders.Select(o => o.ProductId).Distinct().ToList(); + + // Call remote service via generated proxy + var products = await _productIntegrationService.GetProductsByIdsAsync(productIds); + // ... + } +} +``` + +> **Why Integration Services?** Application services are for UI - they have different authorization, validation, and optimization needs. Integration services are designed specifically for inter-service communication. + +**When to use:** Need immediate response, data required to complete current operation (e.g., get product details to display in order list). + +### 2. Distributed Events (Asynchronous) + +Use RabbitMQ-based events for loose coupling. + +**When to use:** +- Notifying other services about state changes (e.g., "order placed", "stock updated") +- Operations that don't need immediate response +- When services should remain independent and decoupled + +```csharp +// Define ETO in Contracts project +[EventName("Product.StockChanged")] +public class StockCountChangedEto +{ + public Guid ProductId { get; set; } + public int NewCount { get; set; } +} + +// Publish +await _distributedEventBus.PublishAsync(new StockCountChangedEto { ... }); + +// Subscribe in another service +public class StockChangedHandler : IDistributedEventHandler, ITransientDependency +{ + public async Task HandleEventAsync(StockCountChangedEto eventData) { ... } +} +``` + +DbContext must implement `IHasEventInbox`, `IHasEventOutbox` for Outbox/Inbox pattern. + +## Performance: Entity Cache + +For frequently accessed data from other services, use Entity Cache: + +```csharp +// Register +context.Services.AddEntityCache(); + +// Use - auto-invalidates on entity changes +private readonly IEntityCache _productCache; + +public async Task GetProductAsync(Guid id) +{ + return await _productCache.GetAsync(id); +} +``` + +## Pre-Configured Infrastructure + +- **RabbitMQ** - Distributed events with Outbox/Inbox +- **Redis** - Distributed cache and locking +- **YARP** - API Gateway +- **OpenIddict** - Auth server + +## Best Practices + +- **Choose communication wisely** - Synchronous for queries needing immediate data, asynchronous for notifications and state changes +- **Use Integration Services** - Not application services for inter-service calls +- **Cache remote data** - Use Entity Cache or IDistributedCache for frequently accessed data +- **Share only Contracts** - Never share implementations +- **Idempotent handlers** - Events may be delivered multiple times +- **Database per service** - Each service owns its database diff --git a/ai-rules/template-specific/module.mdc b/ai-rules/template-specific/module.mdc new file mode 100644 index 0000000000..c60f54239e --- /dev/null +++ b/ai-rules/template-specific/module.mdc @@ -0,0 +1,234 @@ +--- +description: "ABP Module solution template specific patterns" +alwaysApply: false +--- + +# ABP Module Solution Template + +> **Docs**: https://abp.io/docs/latest/solution-templates/application-module + +This template is for developing reusable ABP modules. Key requirement: **extensibility** - consumers must be able to override and customize module behavior. + +## Solution Structure + +``` +MyModule/ +├── src/ +│ ├── MyModule.Domain.Shared/ # Constants, enums, localization +│ ├── MyModule.Domain/ # Entities, repository interfaces, domain services +│ ├── MyModule.Application.Contracts/ # DTOs, service interfaces +│ ├── MyModule.Application/ # Service implementations +│ ├── MyModule.EntityFrameworkCore/ # EF Core implementation +│ ├── MyModule.MongoDB/ # MongoDB implementation +│ ├── MyModule.HttpApi/ # REST controllers +│ ├── MyModule.HttpApi.Client/ # Client proxies +│ ├── MyModule.Web/ # MVC/Razor Pages UI +│ └── MyModule.Blazor/ # Blazor UI +├── test/ +│ └── MyModule.Tests/ +└── host/ + └── MyModule.HttpApi.Host/ # Test host application +``` + +## Database Independence + +Support both EF Core and MongoDB: + +### Repository Interface (Domain) +```csharp +public interface IBookRepository : IRepository +{ + Task FindByNameAsync(string name); + Task> GetListByAuthorAsync(Guid authorId); +} +``` + +### EF Core Implementation +```csharp +public class BookRepository : EfCoreRepository, IBookRepository +{ + public async Task FindByNameAsync(string name) + { + var dbSet = await GetDbSetAsync(); + return await dbSet.FirstOrDefaultAsync(b => b.Name == name); + } +} +``` + +### MongoDB Implementation +```csharp +public class BookRepository : MongoDbRepository, IBookRepository +{ + public async Task FindByNameAsync(string name) + { + var queryable = await GetQueryableAsync(); + return await queryable.FirstOrDefaultAsync(b => b.Name == name); + } +} +``` + +## Table/Collection Prefix + +Allow customization to avoid naming conflicts: + +```csharp +// Domain.Shared +public static class MyModuleDbProperties +{ + public static string DbTablePrefix { get; set; } = "MyModule"; + public static string DbSchema { get; set; } = null; + + public const string ConnectionStringName = "MyModule"; +} +``` + +Usage: +```csharp +builder.Entity(b => +{ + b.ToTable(MyModuleDbProperties.DbTablePrefix + "Books", MyModuleDbProperties.DbSchema); +}); +``` + +## Module Options + +Provide configuration options: + +```csharp +// Domain +public class MyModuleOptions +{ + public bool EnableFeatureX { get; set; } = true; + public int MaxItemCount { get; set; } = 100; +} +``` + +Usage in module: +```csharp +public override void ConfigureServices(ServiceConfigurationContext context) +{ + Configure(options => + { + options.EnableFeatureX = true; + }); +} +``` + +Usage in service: +```csharp +public class MyService : ITransientDependency +{ + private readonly MyModuleOptions _options; + + public MyService(IOptions options) + { + _options = options.Value; + } +} +``` + +## Extensibility Points + +### Virtual Methods (Critical for Modules!) +When developing a reusable module, **all public and protected methods must be virtual** to allow consumers to override behavior: + +```csharp +public class BookAppService : ApplicationService, IBookAppService +{ + // ✅ Public methods MUST be virtual + public virtual async Task CreateAsync(CreateBookDto input) + { + var book = await CreateBookEntityAsync(input); + await _bookRepository.InsertAsync(book); + return _bookMapper.MapToDto(book); + } + + // ✅ Use protected virtual for helper methods (not private) + protected virtual Task CreateBookEntityAsync(CreateBookDto input) + { + return Task.FromResult(new Book( + GuidGenerator.Create(), + input.Name, + input.Price + )); + } + + // ❌ WRONG for modules - private methods cannot be overridden + // private Book CreateBook(CreateBookDto input) { ... } +} +``` + +This allows module consumers to: +- Override specific methods without copying entire class +- Extend functionality while preserving base behavior +- Customize module behavior for their needs + +### Entity Extension +Support object extension system: +```csharp +public class MyModuleModuleExtensionConfigurator +{ + public static void Configure() + { + OneTimeRunner.Run(() => + { + ObjectExtensionManager.Instance.Modules() + .ConfigureMyModule(module => + { + module.ConfigureBook(book => + { + book.AddOrUpdateProperty("CustomProperty"); + }); + }); + }); + } +} +``` + +## Localization + +```csharp +// Domain.Shared +[LocalizationResourceName("MyModule")] +public class MyModuleResource +{ +} + +// Module configuration +Configure(options => +{ + options.Resources + .Add("en") + .AddVirtualJson("/Localization/MyModule"); +}); +``` + +## Permission Definition + +```csharp +public class MyModulePermissionDefinitionProvider : PermissionDefinitionProvider +{ + public override void Define(IPermissionDefinitionContext context) + { + var myGroup = context.AddGroup( + MyModulePermissions.GroupName, + L("Permission:MyModule")); + + myGroup.AddPermission( + MyModulePermissions.Books.Default, + L("Permission:Books")); + } +} +``` + +## Best Practices + +1. **Virtual methods** - All public/protected methods must be `virtual` for extensibility +2. **Protected virtual helpers** - Use `protected virtual` instead of `private` for helper methods +3. **Database agnostic** - Support both EF Core and MongoDB +4. **Configurable** - Use options pattern for customization +5. **Localizable** - Use localization for all user-facing text +6. **Table prefix** - Allow customization to avoid conflicts +7. **Separate connection string** - Support dedicated database +8. **No dependencies on host** - Module should be self-contained +9. **Test with host app** - Include a host application for testing diff --git a/ai-rules/testing/patterns.mdc b/ai-rules/testing/patterns.mdc new file mode 100644 index 0000000000..07c9307448 --- /dev/null +++ b/ai-rules/testing/patterns.mdc @@ -0,0 +1,270 @@ +--- +description: "ABP testing patterns - unit tests and integration tests" +globs: "test/**/*.cs,tests/**/*.cs,**/*Tests*/**/*.cs,**/*Test*.cs" +alwaysApply: false +--- + +# ABP Testing Patterns + +> **Docs**: https://abp.io/docs/latest/testing + +## Test Project Structure + +| Project | Purpose | Base Class | +|---------|---------|------------| +| `*.Domain.Tests` | Domain logic, entities, domain services | `*DomainTestBase` | +| `*.Application.Tests` | Application services | `*ApplicationTestBase` | +| `*.EntityFrameworkCore.Tests` | Repository implementations | `*EntityFrameworkCoreTestBase` | + +## Integration Test Approach + +ABP recommends integration tests over unit tests: +- Tests run with real services and database (SQLite in-memory) +- No mocking of internal services +- Each test gets a fresh database instance + +## Application Service Test + +```csharp +public class BookAppService_Tests : MyProjectApplicationTestBase +{ + private readonly IBookAppService _bookAppService; + + public BookAppService_Tests() + { + _bookAppService = GetRequiredService(); + } + + [Fact] + public async Task Should_Get_List_Of_Books() + { + // Act + var result = await _bookAppService.GetListAsync( + new PagedAndSortedResultRequestDto() + ); + + // Assert + result.TotalCount.ShouldBeGreaterThan(0); + result.Items.ShouldContain(b => b.Name == "Test Book"); + } + + [Fact] + public async Task Should_Create_Book() + { + // Arrange + var input = new CreateBookDto + { + Name = "New Book", + Price = 19.99m + }; + + // Act + var result = await _bookAppService.CreateAsync(input); + + // Assert + result.Id.ShouldNotBe(Guid.Empty); + result.Name.ShouldBe("New Book"); + result.Price.ShouldBe(19.99m); + } + + [Fact] + public async Task Should_Not_Create_Book_With_Invalid_Name() + { + // Arrange + var input = new CreateBookDto + { + Name = "", // Invalid + Price = 10m + }; + + // Act & Assert + await Should.ThrowAsync(async () => + { + await _bookAppService.CreateAsync(input); + }); + } +} +``` + +## Domain Service Test + +```csharp +public class BookManager_Tests : MyProjectDomainTestBase +{ + private readonly BookManager _bookManager; + private readonly IBookRepository _bookRepository; + + public BookManager_Tests() + { + _bookManager = GetRequiredService(); + _bookRepository = GetRequiredService(); + } + + [Fact] + public async Task Should_Create_Book() + { + // Act + var book = await _bookManager.CreateAsync("Test Book", 29.99m); + + // Assert + book.ShouldNotBeNull(); + book.Name.ShouldBe("Test Book"); + book.Price.ShouldBe(29.99m); + } + + [Fact] + public async Task Should_Not_Allow_Duplicate_Book_Name() + { + // Arrange + await _bookManager.CreateAsync("Existing Book", 10m); + + // Act & Assert + var exception = await Should.ThrowAsync(async () => + { + await _bookManager.CreateAsync("Existing Book", 20m); + }); + + exception.Code.ShouldBe("MyProject:BookNameAlreadyExists"); + } +} +``` + +## Test Naming Convention + +Use descriptive names: +```csharp +// Pattern: Should_ExpectedBehavior_When_Condition +public async Task Should_Create_Book_When_Input_Is_Valid() +public async Task Should_Throw_BusinessException_When_Name_Already_Exists() +public async Task Should_Return_Empty_List_When_No_Books_Exist() +``` + +## Arrange-Act-Assert (AAA) + +```csharp +[Fact] +public async Task Should_Update_Book_Price() +{ + // Arrange + var bookId = await CreateTestBookAsync(); + var newPrice = 39.99m; + + // Act + var result = await _bookAppService.UpdateAsync(bookId, new UpdateBookDto + { + Price = newPrice + }); + + // Assert + result.Price.ShouldBe(newPrice); +} +``` + +## Assertions with Shouldly + +ABP uses Shouldly library: +```csharp +result.ShouldNotBeNull(); +result.Name.ShouldBe("Expected Name"); +result.Price.ShouldBeGreaterThan(0); +result.Items.ShouldContain(x => x.Id == expectedId); +result.Items.ShouldBeEmpty(); +result.Items.Count.ShouldBe(5); + +// Exception assertions +await Should.ThrowAsync(async () => +{ + await _service.DoSomethingAsync(); +}); + +var ex = await Should.ThrowAsync(async () => +{ + await _service.DoSomethingAsync(); +}); +ex.Code.ShouldBe("MyProject:ErrorCode"); +``` + +## Test Data Seeding + +```csharp +public class MyProjectTestDataSeedContributor : IDataSeedContributor, ITransientDependency +{ + public static readonly Guid TestBookId = Guid.Parse("..."); + + private readonly IBookRepository _bookRepository; + private readonly IGuidGenerator _guidGenerator; + + public async Task SeedAsync(DataSeedContext context) + { + await _bookRepository.InsertAsync( + new Book(TestBookId, "Test Book", 19.99m, Guid.Empty), + autoSave: true + ); + } +} +``` + +## Disabling Authorization in Tests + +```csharp +public override void ConfigureServices(ServiceConfigurationContext context) +{ + context.Services.AddAlwaysAllowAuthorization(); +} +``` + +## Mocking External Services + +Use NSubstitute when needed: +```csharp +public override void ConfigureServices(ServiceConfigurationContext context) +{ + var emailSender = Substitute.For(); + emailSender.SendAsync(Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(Task.CompletedTask); + + context.Services.AddSingleton(emailSender); +} +``` + +## Testing with Specific User + +```csharp +[Fact] +public async Task Should_Get_Current_User_Books() +{ + // Login as specific user + await WithUnitOfWorkAsync(async () => + { + using (CurrentUser.Change(TestData.UserId)) + { + var result = await _bookAppService.GetMyBooksAsync(); + result.Items.ShouldAllBe(b => b.CreatorId == TestData.UserId); + } + }); +} +``` + +## Testing Multi-Tenancy + +```csharp +[Fact] +public async Task Should_Filter_Books_By_Tenant() +{ + using (CurrentTenant.Change(TestData.TenantId)) + { + var result = await _bookAppService.GetListAsync(new GetBookListDto()); + // Results should be filtered by tenant + } +} +``` + +## Best Practices + +- Each test should be independent +- Don't share state between tests +- Use meaningful test data +- Test edge cases and error conditions +- Keep tests focused on single behavior +- Use test data seeders for common data +- Avoid testing framework internals diff --git a/ai-rules/ui/angular.mdc b/ai-rules/ui/angular.mdc new file mode 100644 index 0000000000..e61881fb28 --- /dev/null +++ b/ai-rules/ui/angular.mdc @@ -0,0 +1,221 @@ +--- +description: "ABP Angular UI patterns and best practices" +globs: "**/angular/**/*.ts,**/angular/**/*.html,**/*.component.ts" +alwaysApply: false +--- + +# ABP Angular UI + +> **Docs**: https://abp.io/docs/latest/framework/ui/angular/overview + +## Project Structure +``` +src/app/ +├── proxy/ # Auto-generated service proxies +├── shared/ # Shared components, pipes, directives +├── book/ # Feature module +│ ├── book.module.ts +│ ├── book-routing.module.ts +│ ├── book-list/ +│ │ ├── book-list.component.ts +│ │ ├── book-list.component.html +│ │ └── book-list.component.scss +│ └── book-detail/ +``` + +## Generate Service Proxies +```bash +abp generate-proxy -t ng +``` + +This generates typed service classes in `src/app/proxy/`. + +## List Component Pattern +```typescript +@Component({ + selector: 'app-book-list', + templateUrl: './book-list.component.html' +}) +export class BookListComponent implements OnInit { + books = { items: [], totalCount: 0 } as PagedResultDto; + + constructor( + public readonly list: ListService, + private bookService: BookService, + private confirmation: ConfirmationService + ) {} + + ngOnInit(): void { + this.hookToQuery(); + } + + private hookToQuery(): void { + this.list.hookToQuery(query => + this.bookService.getList(query) + ).subscribe(response => { + this.books = response; + }); + } + + create(): void { + // Open create modal + } + + delete(book: BookDto): void { + this.confirmation + .warn('::AreYouSureToDelete', '::AreYouSure') + .subscribe(status => { + if (status === Confirmation.Status.confirm) { + this.bookService.delete(book.id).subscribe(() => this.list.get()); + } + }); + } +} +``` + +## Localization +```typescript +// In component +constructor(private localizationService: LocalizationService) {} + +getText(): string { + return this.localizationService.instant('::Books'); +} +``` + +```html + +

{{ '::Books' | abpLocalization }}

+ + +

{{ '::WelcomeMessage' | abpLocalization: userName }}

+``` + +## Authorization + +### Permission Directive +```html + +``` + +### Permission Guard +```typescript +const routes: Routes = [ + { + path: '', + component: BookListComponent, + canActivate: [PermissionGuard], + data: { + requiredPolicy: 'BookStore.Books' + } + } +]; +``` + +### Programmatic Check +```typescript +constructor(private permissionService: PermissionService) {} + +canCreate(): boolean { + return this.permissionService.getGrantedPolicy('BookStore.Books.Create'); +} +``` + +## Forms with Validation +```typescript +@Component({...}) +export class BookFormComponent { + form: FormGroup; + + constructor(private fb: FormBuilder) { + this.buildForm(); + } + + buildForm(): void { + this.form = this.fb.group({ + name: ['', [Validators.required, Validators.maxLength(128)]], + price: [0, [Validators.required, Validators.min(0)]] + }); + } + + save(): void { + if (this.form.invalid) return; + + this.bookService.create(this.form.value).subscribe(() => { + // Handle success + }); + } +} +``` + +```html +
+
+ + +
+ + +
+``` + +## Configuration API +```typescript +constructor(private configService: ConfigStateService) {} + +getCurrentUser(): CurrentUserDto { + return this.configService.getOne('currentUser'); +} + +getSettings(): void { + const setting = this.configService.getSetting('MyApp.MaxItemCount'); +} +``` + +## Modal Service +```typescript +constructor(private modalService: ModalService) {} + +openCreateModal(): void { + const modalRef = this.modalService.open(BookFormComponent, { + size: 'lg' + }); + + modalRef.result.then(result => { + if (result) { + this.list.get(); + } + }); +} +``` + +## Toast Notifications +```typescript +constructor(private toaster: ToasterService) {} + +showSuccess(): void { + this.toaster.success('::BookCreatedSuccessfully', '::Success'); +} + +showError(error: string): void { + this.toaster.error(error, '::Error'); +} +``` + +## Lazy Loading Modules +```typescript +// app-routing.module.ts +const routes: Routes = [ + { + path: 'books', + loadChildren: () => import('./book/book.module').then(m => m.BookModule) + } +]; +``` + +## Theme & Styling +- Use Bootstrap classes +- ABP provides theme variables via CSS custom properties +- Component-specific styles in `.component.scss` diff --git a/ai-rules/ui/blazor.mdc b/ai-rules/ui/blazor.mdc new file mode 100644 index 0000000000..68b8051109 --- /dev/null +++ b/ai-rules/ui/blazor.mdc @@ -0,0 +1,207 @@ +--- +description: "ABP Blazor UI patterns and components" +globs: "**/*.razor,**/Blazor/**/*.cs,**/*.Blazor*/**/*.cs" +alwaysApply: false +--- + +# ABP Blazor UI + +> **Docs**: https://abp.io/docs/latest/framework/ui/blazor/overall + +## Component Base Classes + +### Basic Component +```razor +@inherits AbpComponentBase + +

@L["Books"]

+``` + +### CRUD Page +```razor +@page "/books" +@inherits AbpCrudPageBase + + + + + +

@L["Books"]

+
+ + @if (HasCreatePermission) + { + + } + +
+
+ + + + + + + + + + + + + + + + +
+``` + +## Localization +```razor +@* Using L property from base class *@ +

@L["PageTitle"]

+ +@* With parameters *@ +

@L["WelcomeMessage", CurrentUser.UserName]

+``` + +## Authorization +```razor +@* Check permission before rendering *@ +@if (await AuthorizationService.IsGrantedAsync("MyPermission")) +{ + +} + +@* Using policy-based authorization *@ + + +

You have access!

+
+
+``` + +## Navigation & Menu +Configure in `*MenuContributor.cs`: + +```csharp +public class MyMenuContributor : IMenuContributor +{ + public async Task ConfigureMenuAsync(MenuConfigurationContext context) + { + if (context.Menu.Name == StandardMenus.Main) + { + var bookMenu = new ApplicationMenuItem( + "Books", + l["Menu:Books"], + "/books", + icon: "fa fa-book" + ); + + if (await context.IsGrantedAsync(MyPermissions.Books.Default)) + { + context.Menu.AddItem(bookMenu); + } + } + } +} +``` + +## Notifications & Messages +```csharp +// Success message +await Message.Success(L["BookCreatedSuccessfully"]); + +// Confirmation dialog +if (await Message.Confirm(L["AreYouSure"])) +{ + // User confirmed +} + +// Toast notification +await Notify.Success(L["OperationCompleted"]); +``` + +## Forms & Validation +```razor +
+ + + + @L["Name"] + + + + + + + + +
+``` + +## JavaScript Interop +```csharp +@inject IJSRuntime JsRuntime + +@code { + private async Task CallJavaScript() + { + await JsRuntime.InvokeVoidAsync("myFunction", arg1, arg2); + var result = await JsRuntime.InvokeAsync("myFunctionWithReturn"); + } +} +``` + +## State Management +```csharp +// Inject service proxy from HttpApi.Client +@inject IBookAppService BookAppService + +@code { + private List Books { get; set; } + + protected override async Task OnInitializedAsync() + { + var result = await BookAppService.GetListAsync(new PagedAndSortedResultRequestDto()); + Books = result.Items.ToList(); + } +} +``` + +## Code-Behind Pattern +**Books.razor:** +```razor +@page "/books" +@inherits BooksBase +``` + +**Books.razor.cs:** +```csharp +public partial class Books : BooksBase +{ + // Component logic here +} +``` + +**BooksBase.cs:** +```csharp +public abstract class BooksBase : AbpComponentBase +{ + [Inject] + protected IBookAppService BookAppService { get; set; } +} +``` diff --git a/ai-rules/ui/mvc.mdc b/ai-rules/ui/mvc.mdc new file mode 100644 index 0000000000..1bc423010d --- /dev/null +++ b/ai-rules/ui/mvc.mdc @@ -0,0 +1,258 @@ +--- +description: "ABP MVC and Razor Pages UI patterns" +globs: "**/*.cshtml,**/Pages/**/*.cs,**/Views/**/*.cs,**/Controllers/**/*.cs" +alwaysApply: false +--- + +# ABP MVC / Razor Pages UI + +> **Docs**: https://abp.io/docs/latest/framework/ui/mvc-razor-pages/overall + +## Razor Page Model +```csharp +public class IndexModel : AbpPageModel +{ + private readonly IBookAppService _bookAppService; + + public List Books { get; set; } + + public IndexModel(IBookAppService bookAppService) + { + _bookAppService = bookAppService; + } + + public async Task OnGetAsync() + { + var result = await _bookAppService.GetListAsync( + new PagedAndSortedResultRequestDto() + ); + Books = result.Items.ToList(); + } +} +``` + +## Razor Page View +```html +@page +@model IndexModel + + + + + +

@L["Books"]

+
+ + + +
+
+ + + + + @L["Name"] + @L["Price"] + @L["Actions"] + + + + @foreach (var book in Model.Books) + { + + @book.Name + @book.Price + + + + + } + + + +
+``` + +## ABP Tag Helpers + +### Cards +```html + + Header + Content + Footer + +``` + +### Buttons +```html + + +``` + +### Forms +```html + + + + + + + + + +``` + +### Tables +```html + + + +``` + +## Localization +```html +@* In Razor views/pages *@ +

@L["Books"]

+ +@* With parameters *@ +

@L["WelcomeMessage", Model.UserName]

+``` + +## JavaScript API +```javascript +// Localization +var text = abp.localization.getResource('BookStore')('Books'); + +// Authorization +if (abp.auth.isGranted('BookStore.Books.Create')) { + // Show create button +} + +// Settings +var maxCount = abp.setting.get('BookStore.MaxItemCount'); + +// Ajax with automatic error handling +abp.ajax({ + url: '/api/app/book', + type: 'POST', + data: JSON.stringify(bookData) +}).then(function(result) { + // Success +}); + +// Notifications +abp.notify.success('Book created successfully!'); +abp.notify.error('An error occurred!'); + +// Confirmation +abp.message.confirm('Are you sure?').then(function(confirmed) { + if (confirmed) { + // User confirmed + } +}); +``` + +## DataTables Integration +```javascript +var dataTable = $('#BooksTable').DataTable( + abp.libs.datatables.normalizeConfiguration({ + serverSide: true, + paging: true, + ajax: abp.libs.datatables.createAjax(bookService.getList), + columnDefs: [ + { + title: l('Name'), + data: 'name' + }, + { + title: l('Price'), + data: 'price', + render: function(data) { + return data.toFixed(2); + } + }, + { + title: l('Actions'), + rowAction: { + items: [ + { + text: l('Edit'), + visible: abp.auth.isGranted('BookStore.Books.Edit'), + action: function(data) { + editModal.open({ id: data.record.id }); + } + }, + { + text: l('Delete'), + visible: abp.auth.isGranted('BookStore.Books.Delete'), + confirmMessage: function(data) { + return l('BookDeletionConfirmationMessage', data.record.name); + }, + action: function(data) { + bookService.delete(data.record.id).then(function() { + abp.notify.success(l('SuccessfullyDeleted')); + dataTable.ajax.reload(); + }); + } + } + ] + } + } + ] + }) +); +``` + +## Modal Pages +**CreateModal.cshtml:** +```html +@page +@model CreateModalModel + + + + + + + + + + +``` + +**CreateModal.cshtml.cs:** +```csharp +public class CreateModalModel : AbpPageModel +{ + [BindProperty] + public CreateBookDto Book { get; set; } + + private readonly IBookAppService _bookAppService; + + public CreateModalModel(IBookAppService bookAppService) + { + _bookAppService = bookAppService; + } + + public async Task OnPostAsync() + { + await _bookAppService.CreateAsync(Book); + return NoContent(); + } +} +``` + +## Bundle & Minification +```csharp +Configure(options => +{ + options.StyleBundles.Configure( + StandardBundles.Styles.Global, + bundle => bundle.AddFiles("/styles/my-styles.css") + ); +}); +``` diff --git a/docs/en/get-started/images/abp-studio-background-tasks.png b/docs/en/get-started/images/abp-studio-background-tasks.png index cf63394600..c3d020a90f 100644 Binary files a/docs/en/get-started/images/abp-studio-background-tasks.png and b/docs/en/get-started/images/abp-studio-background-tasks.png differ diff --git a/docs/en/get-started/images/abp-studio-created-microservice-solution-explorer.png b/docs/en/get-started/images/abp-studio-created-microservice-solution-explorer.png index 97936d018e..398be32048 100644 Binary files a/docs/en/get-started/images/abp-studio-created-microservice-solution-explorer.png and b/docs/en/get-started/images/abp-studio-created-microservice-solution-explorer.png differ diff --git a/docs/en/get-started/images/abp-studio-created-new-microservice-solution.png b/docs/en/get-started/images/abp-studio-created-new-microservice-solution.png index 7b13736509..28fa473185 100644 Binary files a/docs/en/get-started/images/abp-studio-created-new-microservice-solution.png and b/docs/en/get-started/images/abp-studio-created-new-microservice-solution.png differ diff --git a/docs/en/get-started/images/abp-studio-microservice-kubernetes-build-docker-images.png b/docs/en/get-started/images/abp-studio-microservice-kubernetes-build-docker-images.png index b2c3115f4b..a4730d1f61 100644 Binary files a/docs/en/get-started/images/abp-studio-microservice-kubernetes-build-docker-images.png and b/docs/en/get-started/images/abp-studio-microservice-kubernetes-build-docker-images.png differ diff --git a/docs/en/get-started/images/abp-studio-microservice-kubernetes-install-helm-chart.png b/docs/en/get-started/images/abp-studio-microservice-kubernetes-install-helm-chart.png index c4e4f6787f..a4f65d9214 100644 Binary files a/docs/en/get-started/images/abp-studio-microservice-kubernetes-install-helm-chart.png and b/docs/en/get-started/images/abp-studio-microservice-kubernetes-install-helm-chart.png differ diff --git a/docs/en/get-started/images/abp-studio-microservice-kubernetes-tab.png b/docs/en/get-started/images/abp-studio-microservice-kubernetes-tab.png index 16ff718210..400a4d1789 100644 Binary files a/docs/en/get-started/images/abp-studio-microservice-kubernetes-tab.png and b/docs/en/get-started/images/abp-studio-microservice-kubernetes-tab.png differ diff --git a/docs/en/get-started/images/abp-studio-microservice-solution-runner-applications.png b/docs/en/get-started/images/abp-studio-microservice-solution-runner-applications.png index 2bd1a2fe40..497ad5d065 100644 Binary files a/docs/en/get-started/images/abp-studio-microservice-solution-runner-applications.png and b/docs/en/get-started/images/abp-studio-microservice-solution-runner-applications.png differ diff --git a/docs/en/get-started/images/abp-studio-microservice-solution-runner-browse-microservice.png b/docs/en/get-started/images/abp-studio-microservice-solution-runner-browse-microservice.png index 6b15ce18b3..49abf55666 100644 Binary files a/docs/en/get-started/images/abp-studio-microservice-solution-runner-browse-microservice.png and b/docs/en/get-started/images/abp-studio-microservice-solution-runner-browse-microservice.png differ diff --git a/docs/en/get-started/images/abp-studio-microservice-solution-runner-browse.png b/docs/en/get-started/images/abp-studio-microservice-solution-runner-browse.png index da82711582..b633385243 100644 Binary files a/docs/en/get-started/images/abp-studio-microservice-solution-runner-browse.png and b/docs/en/get-started/images/abp-studio-microservice-solution-runner-browse.png differ diff --git a/docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch-1.png b/docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch-1.png index 1d953a9552..aba03aedf4 100644 Binary files a/docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch-1.png and b/docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch-1.png differ diff --git a/docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch-2.png b/docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch-2.png index 53152f1243..d3275453ab 100644 Binary files a/docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch-2.png and b/docs/en/get-started/images/abp-studio-microservice-solution-runner-enable-watch-2.png differ diff --git a/docs/en/get-started/images/abp-studio-microservice-solution-runner-external-service.png b/docs/en/get-started/images/abp-studio-microservice-solution-runner-external-service.png index 35fc51899e..d02ac37d39 100644 Binary files a/docs/en/get-started/images/abp-studio-microservice-solution-runner-external-service.png and b/docs/en/get-started/images/abp-studio-microservice-solution-runner-external-service.png differ diff --git a/docs/en/get-started/images/abp-studio-microservice-solution-runner-watch-enabled-icon.png b/docs/en/get-started/images/abp-studio-microservice-solution-runner-watch-enabled-icon.png index 796d183ddc..d3539efffb 100644 Binary files a/docs/en/get-started/images/abp-studio-microservice-solution-runner-watch-enabled-icon.png and b/docs/en/get-started/images/abp-studio-microservice-solution-runner-watch-enabled-icon.png differ diff --git a/docs/en/get-started/images/abp-studio-microservice-solution-runner.png b/docs/en/get-started/images/abp-studio-microservice-solution-runner.png index 3e06813a46..4a856a62e5 100644 Binary files a/docs/en/get-started/images/abp-studio-microservice-solution-runner.png and b/docs/en/get-started/images/abp-studio-microservice-solution-runner.png differ diff --git a/docs/en/get-started/images/abp-studio-new-microservice-helm-charts.png b/docs/en/get-started/images/abp-studio-new-microservice-helm-charts.png index 42009900b3..de4476a9f1 100644 Binary files a/docs/en/get-started/images/abp-studio-new-microservice-helm-charts.png and b/docs/en/get-started/images/abp-studio-new-microservice-helm-charts.png differ diff --git a/docs/en/get-started/images/abp-studio-new-microservice-solution-dialog-optional-modules.png b/docs/en/get-started/images/abp-studio-new-microservice-solution-dialog-optional-modules.png index 3b1a4abe68..0ecc98f3ee 100644 Binary files a/docs/en/get-started/images/abp-studio-new-microservice-solution-dialog-optional-modules.png and b/docs/en/get-started/images/abp-studio-new-microservice-solution-dialog-optional-modules.png differ diff --git a/docs/en/get-started/images/abp-studio-new-microservice-solution-dialog-properties.png b/docs/en/get-started/images/abp-studio-new-microservice-solution-dialog-properties.png index 5f2a9b7938..13d0e679a8 100644 Binary files a/docs/en/get-started/images/abp-studio-new-microservice-solution-dialog-properties.png and b/docs/en/get-started/images/abp-studio-new-microservice-solution-dialog-properties.png differ diff --git a/docs/en/get-started/images/abp-studio-new-solution-dialog-additional-options-microservice.png b/docs/en/get-started/images/abp-studio-new-solution-dialog-additional-options-microservice.png index cb85c3a2b8..ae89059631 100644 Binary files a/docs/en/get-started/images/abp-studio-new-solution-dialog-additional-options-microservice.png and b/docs/en/get-started/images/abp-studio-new-solution-dialog-additional-options-microservice.png differ diff --git a/docs/en/get-started/images/abp-studio-new-solution-dialog-additional-services.png b/docs/en/get-started/images/abp-studio-new-solution-dialog-additional-services.png index 6bbf3fb22a..40f2037ac5 100644 Binary files a/docs/en/get-started/images/abp-studio-new-solution-dialog-additional-services.png and b/docs/en/get-started/images/abp-studio-new-solution-dialog-additional-services.png differ diff --git a/docs/en/get-started/images/abp-studio-new-solution-dialog-admin-password.png b/docs/en/get-started/images/abp-studio-new-solution-dialog-admin-password.png new file mode 100644 index 0000000000..8040b491a3 Binary files /dev/null and b/docs/en/get-started/images/abp-studio-new-solution-dialog-admin-password.png differ diff --git a/docs/en/get-started/images/abp-studio-new-solution-dialog-aspire-configuration-microservice.png b/docs/en/get-started/images/abp-studio-new-solution-dialog-aspire-configuration-microservice.png index 74cd5066bb..507fdf0a7f 100644 Binary files a/docs/en/get-started/images/abp-studio-new-solution-dialog-aspire-configuration-microservice.png and b/docs/en/get-started/images/abp-studio-new-solution-dialog-aspire-configuration-microservice.png differ diff --git a/docs/en/get-started/images/abp-studio-new-solution-dialog-database-configurations-microservice.png b/docs/en/get-started/images/abp-studio-new-solution-dialog-database-configurations-microservice.png index e41580ad1e..12f0003333 100644 Binary files a/docs/en/get-started/images/abp-studio-new-solution-dialog-database-configurations-microservice.png and b/docs/en/get-started/images/abp-studio-new-solution-dialog-database-configurations-microservice.png differ diff --git a/docs/en/get-started/images/abp-studio-new-solution-dialog-database-provider-microservice.png b/docs/en/get-started/images/abp-studio-new-solution-dialog-database-provider-microservice.png index ed52bac62c..97255f2464 100644 Binary files a/docs/en/get-started/images/abp-studio-new-solution-dialog-database-provider-microservice.png and b/docs/en/get-started/images/abp-studio-new-solution-dialog-database-provider-microservice.png differ diff --git a/docs/en/get-started/images/abp-studio-new-solution-dialog-dynamic-localization.png b/docs/en/get-started/images/abp-studio-new-solution-dialog-dynamic-localization.png index 455a6bd574..3564685c5c 100644 Binary files a/docs/en/get-started/images/abp-studio-new-solution-dialog-dynamic-localization.png and b/docs/en/get-started/images/abp-studio-new-solution-dialog-dynamic-localization.png differ diff --git a/docs/en/get-started/images/abp-studio-new-solution-dialog-languages-microservice.png b/docs/en/get-started/images/abp-studio-new-solution-dialog-languages-microservice.png index 705fee9325..1797a4553c 100644 Binary files a/docs/en/get-started/images/abp-studio-new-solution-dialog-languages-microservice.png and b/docs/en/get-started/images/abp-studio-new-solution-dialog-languages-microservice.png differ diff --git a/docs/en/get-started/images/abp-studio-new-solution-dialog-microservice.png b/docs/en/get-started/images/abp-studio-new-solution-dialog-microservice.png index 9b9210b269..d1ec05e4c8 100644 Binary files a/docs/en/get-started/images/abp-studio-new-solution-dialog-microservice.png and b/docs/en/get-started/images/abp-studio-new-solution-dialog-microservice.png differ diff --git a/docs/en/get-started/images/abp-studio-new-solution-dialog-mobile-framework-microservice.png b/docs/en/get-started/images/abp-studio-new-solution-dialog-mobile-framework-microservice.png index 04e1af7ebe..004a473579 100644 Binary files a/docs/en/get-started/images/abp-studio-new-solution-dialog-mobile-framework-microservice.png and b/docs/en/get-started/images/abp-studio-new-solution-dialog-mobile-framework-microservice.png differ diff --git a/docs/en/get-started/images/abp-studio-new-solution-dialog-multi-tenancy.png b/docs/en/get-started/images/abp-studio-new-solution-dialog-multi-tenancy.png index 3c06ae001d..3311e76a76 100644 Binary files a/docs/en/get-started/images/abp-studio-new-solution-dialog-multi-tenancy.png and b/docs/en/get-started/images/abp-studio-new-solution-dialog-multi-tenancy.png differ diff --git a/docs/en/get-started/images/abp-studio-new-solution-dialog-public-web-site.png b/docs/en/get-started/images/abp-studio-new-solution-dialog-public-web-site.png index 40325a5358..edad8a63db 100644 Binary files a/docs/en/get-started/images/abp-studio-new-solution-dialog-public-web-site.png and b/docs/en/get-started/images/abp-studio-new-solution-dialog-public-web-site.png differ diff --git a/docs/en/get-started/images/abp-studio-new-solution-dialog-ui-framework-microservice.png b/docs/en/get-started/images/abp-studio-new-solution-dialog-ui-framework-microservice.png index 27e39f3776..08e30001e6 100644 Binary files a/docs/en/get-started/images/abp-studio-new-solution-dialog-ui-framework-microservice.png and b/docs/en/get-started/images/abp-studio-new-solution-dialog-ui-framework-microservice.png differ diff --git a/docs/en/get-started/images/abp-studio-new-solution-dialog-ui-theme-microservice.png b/docs/en/get-started/images/abp-studio-new-solution-dialog-ui-theme-microservice.png index 53692d03de..8755073d1d 100644 Binary files a/docs/en/get-started/images/abp-studio-new-solution-dialog-ui-theme-microservice.png and b/docs/en/get-started/images/abp-studio-new-solution-dialog-ui-theme-microservice.png differ diff --git a/docs/en/get-started/images/abp-studio-open-module-folder.png b/docs/en/get-started/images/abp-studio-open-module-folder.png index b32f046d94..d945377143 100644 Binary files a/docs/en/get-started/images/abp-studio-open-module-folder.png and b/docs/en/get-started/images/abp-studio-open-module-folder.png differ diff --git a/docs/en/get-started/images/abp-studio-welcome-screen.png b/docs/en/get-started/images/abp-studio-welcome-screen.png index 938be4a010..0607762677 100644 Binary files a/docs/en/get-started/images/abp-studio-welcome-screen.png and b/docs/en/get-started/images/abp-studio-welcome-screen.png differ diff --git a/docs/en/get-started/microservice.md b/docs/en/get-started/microservice.md index 56f275c258..d30e3926fc 100644 --- a/docs/en/get-started/microservice.md +++ b/docs/en/get-started/microservice.md @@ -126,6 +126,12 @@ Click the Next button to see *Additional Services* screen: On that screen, allows you to include extra microservices in your ABP solution during the creation process. This feature lets you extend your solution with business-specific services right from the start. +Click the Next button to see *Admin Password* screen: + +![abp-studio-new-solution-dialog-admin-password](images/abp-studio-new-solution-dialog-admin-password.png) + +Here, you can set the initial password for the `admin` user of your application. By default, it is set to `1q2w3E*`, but you can change it to a more secure password of your choice. + Now, we are ready to allow ABP Studio to create our solution. Just click the *Create* button and let the ABP Studio do the rest for you. After clicking the *Create* button, the dialog is closed and your solution is loaded into ABP Studio: ![abp-studio-created-new-microservice-solution](images/abp-studio-created-new-microservice-solution.png) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj b/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj index 078d1aa6cc..23cb5c2b33 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj +++ b/framework/src/Volo.Abp.Cli.Core/Volo.Abp.Cli.Core.csproj @@ -14,6 +14,7 @@ + diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs index 8ff8ad3206..a188137ea2 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs @@ -79,6 +79,7 @@ public class AbpCliCoreModule : AbpModule options.Commands[ClearDownloadCacheCommand.Name] = typeof(ClearDownloadCacheCommand); options.Commands[RecreateInitialMigrationCommand.Name] = typeof(RecreateInitialMigrationCommand); options.Commands[GenerateRazorPage.Name] = typeof(GenerateRazorPage); + options.Commands[McpCommand.Name] = typeof(McpCommand); options.DisabledModulesToAddToSolution.Add("Volo.Abp.LeptonXTheme.Pro"); options.DisabledModulesToAddToSolution.Add("Volo.Abp.LeptonXTheme.Lite"); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Args/CommandLineArgsExtensions.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Args/CommandLineArgsExtensions.cs new file mode 100644 index 0000000000..e9ae5ba77c --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Args/CommandLineArgsExtensions.cs @@ -0,0 +1,11 @@ +using Volo.Abp.Cli.Commands; + +namespace Volo.Abp.Cli.Args; + +public static class CommandLineArgsExtensions +{ + public static bool IsMcpCommand(this CommandLineArgs args) + { + return args.IsCommand(McpCommand.Name); + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliConsts.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliConsts.cs index 78a36fe329..43436329fb 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliConsts.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliConsts.cs @@ -1,4 +1,4 @@ -namespace Volo.Abp.Cli; +namespace Volo.Abp.Cli; public static class CliConsts { @@ -20,8 +20,12 @@ public static class CliConsts public static string AppSettingsSecretJsonFileName = "appsettings.secrets.json"; + public const string McpLogLevelEnvironmentVariable = "ABP_MCP_LOG_LEVEL"; + public const string DefaultMcpServerUrl = "https://mcp.abp.io"; + public static class MemoryKeys { public const string LatestCliVersionCheckDate = "LatestCliVersionCheckDate"; + public const string McpToolsLastFetchDate = "McpToolsLastFetchDate"; } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliPaths.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliPaths.cs index d47987b220..537c794c15 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliPaths.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliPaths.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Text; @@ -14,6 +14,9 @@ public static class CliPaths public static string Memory => Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)!, "memory.bin"); public static string Build => Path.Combine(AbpRootPath, "build"); public static string Lic => Path.Combine(Path.GetTempPath(), Encoding.ASCII.GetString(new byte[] { 65, 98, 112, 76, 105, 99, 101, 110, 115, 101, 46, 98, 105, 110 })); + public static string McpToolsCache => Path.Combine(Root, "mcp-tools.json"); + public static string McpLog => Path.Combine(Log, "mcp.log"); + public static string McpConfig => Path.Combine(Root, "mcp-config.json"); public static readonly string AbpRootPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".abp"); } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs index 063ceddebf..52bbac8e43 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/CliService.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using NuGet.Versioning; @@ -10,6 +10,7 @@ using System.Reflection; using System.Threading.Tasks; using Volo.Abp.Cli.Args; using Volo.Abp.Cli.Commands; +using Volo.Abp.Cli.Commands.Services; using Volo.Abp.Cli.Memory; using Volo.Abp.Cli.Version; using Volo.Abp.Cli.Utils; @@ -21,8 +22,11 @@ namespace Volo.Abp.Cli; public class CliService : ITransientDependency { + private const string McpLogSource = nameof(CliService); + private readonly MemoryService _memoryService; private readonly ITelemetryService _telemetryService; + private readonly IMcpLogger _mcpLogger; public ILogger Logger { get; set; } protected ICommandLineArgumentParser CommandLineArgumentParser { get; } protected ICommandSelector CommandSelector { get; } @@ -39,7 +43,8 @@ public class CliService : ITransientDependency ICmdHelper cmdHelper, MemoryService memoryService, CliVersionService cliVersionService, - ITelemetryService telemetryService) + ITelemetryService telemetryService, + IMcpLogger mcpLogger) { _memoryService = memoryService; CommandLineArgumentParser = commandLineArgumentParser; @@ -49,19 +54,27 @@ public class CliService : ITransientDependency CmdHelper = cmdHelper; CliVersionService = cliVersionService; _telemetryService = telemetryService; + _mcpLogger = mcpLogger; Logger = NullLogger.Instance; } public async Task RunAsync(string[] args) { - var currentCliVersion = await CliVersionService.GetCurrentCliVersionAsync(); - Logger.LogInformation($"ABP CLI {currentCliVersion}"); - var commandLineArgs = CommandLineArgumentParser.Parse(args); + var currentCliVersion = await CliVersionService.GetCurrentCliVersionAsync(); + + var isMcpCommand = commandLineArgs.IsMcpCommand(); + + // Don't print banner for MCP command to avoid corrupting stdout JSON-RPC stream + if (!isMcpCommand) + { + Logger.LogInformation($"ABP CLI {currentCliVersion}"); + } #if !DEBUG - if (!commandLineArgs.Options.ContainsKey("skip-cli-version-check")) + // Skip version check for MCP command to avoid corrupting stdout JSON-RPC stream + if (!isMcpCommand && !commandLineArgs.Options.ContainsKey("skip-cli-version-check")) { await CheckCliVersionAsync(currentCliVersion); } @@ -85,13 +98,29 @@ public class CliService : ITransientDependency } catch (CliUsageException usageException) { - Logger.LogWarning(usageException.Message); + // For MCP command, use IMcpLogger to avoid corrupting stdout JSON-RPC stream + if (commandLineArgs.IsMcpCommand()) + { + _mcpLogger.Error(McpLogSource, usageException.Message); + } + else + { + Logger.LogWarning(usageException.Message); + } Environment.ExitCode = 1; } catch (Exception ex) { await _telemetryService.AddErrorActivityAsync(ex.Message); - Logger.LogException(ex); + // For MCP command, use IMcpLogger to avoid corrupting stdout JSON-RPC stream + if (commandLineArgs.IsMcpCommand()) + { + _mcpLogger.Error(McpLogSource, "Fatal error", ex); + } + else + { + Logger.LogException(ex); + } throw; } finally diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CommandSelector.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CommandSelector.cs index 9bfdcfac5c..a409614712 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CommandSelector.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/CommandSelector.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options; using System; using System.Collections.Generic; using Volo.Abp.Cli.Args; diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/HelpCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/HelpCommand.cs index cc13e9d187..c051f6d455 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/HelpCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/HelpCommand.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Internal/RecreateInitialMigrationCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Internal/RecreateInitialMigrationCommand.cs index 2b65b9d00b..c3641cac15 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Internal/RecreateInitialMigrationCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Internal/RecreateInitialMigrationCommand.cs @@ -54,6 +54,14 @@ public class RecreateInitialMigrationCommand : IConsoleCommand, ITransientDepend Directory.Delete(Path.Combine(projectDir, "TenantMigrations"), true); separateDbContext = true; } + + CmdHelper.RunCmd("dotnet build", workingDirectory: projectDir, exitCode: out var exitCode); + if (exitCode != 0) + { + Logger.LogError("Build failed for project {Project}. Skipping migration recreation.", csprojFile); + continue; + } + if (!separateDbContext) { CmdHelper.RunCmd($"dotnet ef migrations add Initial", workingDirectory: projectDir); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/McpCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/McpCommand.cs new file mode 100644 index 0000000000..ebf59e2ab6 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/McpCommand.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Volo.Abp.Cli.Args; +using Volo.Abp.Cli.Auth; +using Volo.Abp.Cli.Commands.Models; +using Volo.Abp.Cli.Commands.Services; +using Volo.Abp.Cli.Licensing; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Internal.Telemetry; +using Volo.Abp.Internal.Telemetry.Constants; + +namespace Volo.Abp.Cli.Commands; + +public class McpCommand : IConsoleCommand, ITransientDependency +{ + private const string LogSource = nameof(McpCommand); + public const string Name = "mcp"; + + private readonly AuthService _authService; + private readonly IApiKeyService _apiKeyService; + private readonly McpServerService _mcpServerService; + private readonly McpHttpClientService _mcpHttpClient; + private readonly IMcpLogger _mcpLogger; + private readonly ITelemetryService _telemetryService; + + public ILogger Logger { get; set; } + + public McpCommand( + IApiKeyService apiKeyService, + AuthService authService, + McpServerService mcpServerService, + McpHttpClientService mcpHttpClient, + IMcpLogger mcpLogger, + ITelemetryService telemetryService) + { + _apiKeyService = apiKeyService; + _authService = authService; + _mcpServerService = mcpServerService; + _mcpHttpClient = mcpHttpClient; + _mcpLogger = mcpLogger; + _telemetryService = telemetryService; + Logger = NullLogger.Instance; + } + + public async Task ExecuteAsync(CommandLineArgs commandLineArgs) + { + await ValidateLicenseAsync(); + + var option = commandLineArgs.Target; + + if (!string.IsNullOrEmpty(option) && option.Equals("get-config", StringComparison.OrdinalIgnoreCase)) + { + await PrintConfigurationAsync(); + return; + } + + await using var _ = _telemetryService.TrackActivityAsync(ActivityNameConsts.AbpCliCommandsMcp); + + // Check server health before starting - fail if not reachable + _mcpLogger.Info(LogSource, "Checking ABP.IO MCP Server connection..."); + var isHealthy = await _mcpHttpClient.CheckServerHealthAsync(); + + if (!isHealthy) + { + throw new CliUsageException( + "Could not connect to ABP.IO MCP Server. " + + "The MCP server requires a connection to fetch tool definitions. " + + "Please check your internet connection and try again."); + } + + _mcpLogger.Info(LogSource, "Starting ABP MCP Server..."); + + var cts = new CancellationTokenSource(); + + ConsoleCancelEventHandler cancelHandler = (sender, e) => + { + e.Cancel = true; + _mcpLogger.Info(LogSource, "Shutting down ABP MCP Server..."); + + try + { + cts.Cancel(); + } + catch (ObjectDisposedException) + { + // CTS already disposed + } + }; + + Console.CancelKeyPress += cancelHandler; + + try + { + await _mcpServerService.RunAsync(cts.Token); + } + catch (OperationCanceledException) + { + // Expected when Ctrl+C is pressed + } + catch (Exception ex) + { + _mcpLogger.Error(LogSource, "Error running MCP server", ex); + throw; + } + finally + { + Console.CancelKeyPress -= cancelHandler; + cts.Dispose(); + } + } + + private async Task ValidateLicenseAsync() + { + var loginInfo = await _authService.GetLoginInfoAsync(); + + if (string.IsNullOrEmpty(loginInfo?.Organization)) + { + throw new CliUsageException("Please log in with your account!"); + } + + var licenseResult = await _apiKeyService.GetApiKeyOrNullAsync(); + + if (licenseResult == null || !licenseResult.HasActiveLicense) + { + var errorMessage = licenseResult?.ErrorMessage ?? "No active license found."; + throw new CliUsageException(errorMessage); + } + + if (licenseResult.LicenseEndTime.HasValue && licenseResult.LicenseEndTime.Value < DateTime.UtcNow) + { + throw new CliUsageException("Your license has expired. Please renew your license to use the MCP server."); + } + } + + private Task PrintConfigurationAsync() + { + var config = new McpClientConfiguration + { + McpServers = new Dictionary + { + ["abp"] = new McpServerConfig + { + Command = "abp", + Args = new List { "mcp" }, + Env = new Dictionary() + } + } + }; + + var json = JsonSerializer.Serialize(config, new JsonSerializerOptions + { + WriteIndented = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }); + + Console.WriteLine(json); + + return Task.CompletedTask; + } + + public string GetUsageInfo() + { + var sb = new StringBuilder(); + + sb.AppendLine(""); + sb.AppendLine("Usage:"); + sb.AppendLine(""); + sb.AppendLine(" abp mcp [options]"); + sb.AppendLine(""); + sb.AppendLine("Options:"); + sb.AppendLine(""); + sb.AppendLine(" (start the local MCP server)"); + sb.AppendLine("get-config (print MCP client configuration as JSON)"); + sb.AppendLine(""); + sb.AppendLine("Examples:"); + sb.AppendLine(""); + sb.AppendLine(" abp mcp"); + sb.AppendLine(" abp mcp get-config"); + sb.AppendLine(""); + + return sb.ToString(); + } + + public static string GetShortDescription() + { + return "Runs the local MCP server and outputs client configuration for AI tool integration."; + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Models/McpClientConfiguration.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Models/McpClientConfiguration.cs new file mode 100644 index 0000000000..60b21cbb33 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Models/McpClientConfiguration.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Volo.Abp.Cli.Commands.Models; + +public class McpClientConfiguration +{ + [JsonPropertyName("mcpServers")] + public Dictionary McpServers { get; set; } = new(); +} + +public class McpServerConfig +{ + [JsonPropertyName("command")] + public string Command { get; set; } + + [JsonPropertyName("args")] + public List Args { get; set; } = new(); + + [JsonPropertyName("env")] + public Dictionary Env { get; set; } +} + diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Models/McpToolDefinition.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Models/McpToolDefinition.cs new file mode 100644 index 0000000000..f59776bc0a --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Models/McpToolDefinition.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.Text.Json; + +namespace Volo.Abp.Cli.Commands.Models; + +public class McpToolDefinition +{ + public string Name { get; set; } + public string Description { get; set; } + public McpToolInputSchema InputSchema { get; set; } + public JsonElement? OutputSchema { get; set; } +} + +public class McpToolInputSchema +{ + public string Type { get; set; } = "object"; + public Dictionary Properties { get; set; } + public List Required { get; set; } +} + +public class McpToolProperty +{ + public string Type { get; set; } + public string Description { get; set; } +} + diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/AbpMcpServerTool.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/AbpMcpServerTool.cs new file mode 100644 index 0000000000..6858cf7571 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/AbpMcpServerTool.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using ModelContextProtocol.Protocol; +using ModelContextProtocol.Server; + +namespace Volo.Abp.Cli.Commands.Services; + +internal class AbpMcpServerTool : McpServerTool +{ + private readonly string _name; + private readonly string _description; + private readonly JsonElement _inputSchema; + private readonly JsonElement? _outputSchema; + private readonly Func, CancellationToken, ValueTask> _handler; + + public AbpMcpServerTool( + string name, + string description, + JsonElement inputSchema, + JsonElement? outputSchema, + Func, CancellationToken, ValueTask> handler) + { + _name = name; + _description = description; + _inputSchema = inputSchema; + _outputSchema = outputSchema; + _handler = handler; + } + + public override Tool ProtocolTool => new Tool + { + Name = _name, + Description = _description, + InputSchema = _inputSchema, + OutputSchema = _outputSchema + }; + + public override IReadOnlyList Metadata => Array.Empty(); + + public override ValueTask InvokeAsync(RequestContext context, CancellationToken cancellationToken) + { + return _handler(context, cancellationToken); + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/IMcpLogger.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/IMcpLogger.cs new file mode 100644 index 0000000000..f579420a1e --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/IMcpLogger.cs @@ -0,0 +1,36 @@ +using System; + +namespace Volo.Abp.Cli.Commands.Services; + +/// +/// Logger interface for MCP operations. +/// Writes detailed logs to file and critical messages (Warning/Error) to stderr. +/// Log level is controlled via ABP_MCP_LOG_LEVEL environment variable. +/// +public interface IMcpLogger +{ + /// + /// Logs a debug message. Only written to file when log level is Debug. + /// + void Debug(string source, string message); + + /// + /// Logs an informational message. Written to file when log level is Debug or Info. + /// + void Info(string source, string message); + + /// + /// Logs a warning message. Written to file and stderr. + /// + void Warning(string source, string message); + + /// + /// Logs an error message. Written to file and stderr. + /// + void Error(string source, string message); + + /// + /// Logs an error message with exception details. Written to file and stderr. + /// + void Error(string source, string message, Exception exception); +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/InitialMigrationCreator.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/InitialMigrationCreator.cs index 860fcef60f..2288eeccf7 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/InitialMigrationCreator.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/InitialMigrationCreator.cs @@ -14,7 +14,7 @@ public class InitialMigrationCreator : ITransientDependency public ICmdHelper CmdHelper { get; } public DotnetEfToolManager DotnetEfToolManager { get; } public ILogger Logger { get; set; } - + public InitialMigrationCreator(ICmdHelper cmdHelper, DotnetEfToolManager dotnetEfToolManager) { CmdHelper = cmdHelper; @@ -30,11 +30,11 @@ public class InitialMigrationCreator : ITransientDependency Logger.LogError($"This path doesn't exist: {targetProjectFolder}"); return false; } - + Logger.LogInformation("Creating initial migrations..."); await DotnetEfToolManager.BeSureInstalledAsync(); - + var tenantDbContextName = FindTenantDbContextName(targetProjectFolder); var dbContextName = tenantDbContextName != null ? FindDbContextName(targetProjectFolder) @@ -60,7 +60,7 @@ public class InitialMigrationCreator : ITransientDependency return migrationSuccess; } - + private string FindTenantDbContextName(string projectFolder) { var tenantDbContext = Directory.GetFiles(projectFolder, "*TenantMigrationsDbContext.cs", SearchOption.AllDirectories) @@ -93,6 +93,12 @@ public class InitialMigrationCreator : ITransientDependency private string AddMigrationAndGetOutput(string dbMigrationsFolder, string dbContext, string outputDirectory) { + var output = CmdHelper.RunCmdAndGetOutput("dotnet build", out int buildExitCode, dbMigrationsFolder); + if (buildExitCode != 0) + { + return output; + } + var dbContextOption = string.IsNullOrWhiteSpace(dbContext) ? string.Empty : $"--context {dbContext}"; @@ -108,4 +114,4 @@ public class InitialMigrationCreator : ITransientDependency output.Contains("To undo this action") && output.Contains("ef migrations remove")); } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/McpHttpClientService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/McpHttpClientService.cs new file mode 100644 index 0000000000..c15a6c06b6 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/McpHttpClientService.cs @@ -0,0 +1,280 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Volo.Abp.Cli.Commands.Models; +using Volo.Abp.Cli.Http; +using Volo.Abp.DependencyInjection; +using Volo.Abp.IO; + +namespace Volo.Abp.Cli.Commands.Services; + +public class McpHttpClientService : ISingletonDependency +{ + private static readonly JsonSerializerOptions JsonSerializerOptionsWeb = new(JsonSerializerDefaults.Web); + + private const string LogSource = nameof(McpHttpClientService); + + private readonly CliHttpClientFactory _httpClientFactory; + private readonly ILogger _logger; + private readonly IMcpLogger _mcpLogger; + private readonly Lazy> _cachedServerUrlLazy; + private List _validToolNames; + private bool _toolDefinitionsLoaded; + + public McpHttpClientService( + CliHttpClientFactory httpClientFactory, + ILogger logger, + IMcpLogger mcpLogger) + { + _httpClientFactory = httpClientFactory; + _logger = logger; + _mcpLogger = mcpLogger; + _cachedServerUrlLazy = new Lazy>(GetMcpServerUrlInternalAsync); + } + + public void InitializeToolNames(List tools) + { + _validToolNames = tools.Select(t => t.Name).ToList(); + _toolDefinitionsLoaded = true; + _mcpLogger.Debug(LogSource, $"Initialized tool names from cache. Count={tools.Count}, Instance={GetHashCode()}"); + } + + public async Task CallToolAsync(string toolName, JsonElement arguments) + { + _mcpLogger.Debug(LogSource, $"CallToolAsync called for '{toolName}'. _toolDefinitionsLoaded={_toolDefinitionsLoaded}, Instance={GetHashCode()}"); + + if (!_toolDefinitionsLoaded) + { + throw new CliUsageException("Tool definitions have not been loaded yet. This is an internal error."); + } + + // Validate toolName against whitelist to prevent malicious input + if (_validToolNames != null && !_validToolNames.Contains(toolName)) + { + _mcpLogger.Warning(LogSource, $"Attempted to call unknown tool: {toolName}"); + return CreateErrorResponse($"Unknown tool: {toolName}"); + } + + var baseUrl = await GetMcpServerUrlAsync(); + var url = $"{baseUrl}/tools/call"; + + try + { + using var httpClient = _httpClientFactory.CreateClient(needsAuthentication: true); + + var jsonContent = JsonSerializer.Serialize( + new { name = toolName, arguments }, + JsonSerializerOptionsWeb); + + var content = new StringContent(jsonContent, Encoding.UTF8, "application/json"); + + var response = await httpClient.PostAsync(url, content); + + if (!response.IsSuccessStatusCode) + { + _mcpLogger.Error(LogSource, $"API call failed with status: {response.StatusCode}"); + + // Return sanitized error message to client + var errorMessage = GetSanitizedHttpErrorMessage(response.StatusCode); + return CreateErrorResponse(errorMessage); + } + + return await response.Content.ReadAsStringAsync(); + } + catch (HttpRequestException ex) + { + _mcpLogger.Error(LogSource, $"Network error calling tool '{toolName}'", ex); + + // Return sanitized error to client + return CreateErrorResponse(ErrorMessages.NetworkConnectivity); + } + catch (TaskCanceledException ex) + { + _mcpLogger.Error(LogSource, $"Timeout calling tool '{toolName}'", ex); + + // Return sanitized error to client + return CreateErrorResponse(ErrorMessages.Timeout); + } + catch (Exception ex) + { + _mcpLogger.Error(LogSource, $"Unexpected error calling tool '{toolName}'", ex); + + // Return generic sanitized error to client + return CreateErrorResponse(ErrorMessages.Unexpected); + } + } + + public async Task CheckServerHealthAsync() + { + var baseUrl = await GetMcpServerUrlAsync(); + + try + { + using var httpClient = _httpClientFactory.CreateClient(needsAuthentication: false); + var response = await httpClient.GetAsync(baseUrl); + return response.IsSuccessStatusCode; + } + catch (Exception) + { + // Silently fail health check - it's optional + return false; + } + } + + public async Task> GetToolDefinitionsAsync() + { + _mcpLogger.Debug(LogSource, $"GetToolDefinitionsAsync called. Instance={GetHashCode()}"); + + var baseUrl = await GetMcpServerUrlAsync(); + var url = $"{baseUrl}/tools"; + + try + { + using var httpClient = _httpClientFactory.CreateClient(needsAuthentication: true); + var response = await httpClient.GetAsync(url); + + if (!response.IsSuccessStatusCode) + { + _mcpLogger.Error(LogSource, $"Failed to fetch tool definitions with status: {response.StatusCode}"); + + // Throw sanitized exception + var errorMessage = GetSanitizedHttpErrorMessage(response.StatusCode); + throw new CliUsageException($"Failed to fetch tool definitions: {errorMessage}"); + } + + var responseContent = await response.Content.ReadAsStringAsync(); + + // The API returns { tools: [...] } format + var result = JsonSerializer.Deserialize(responseContent, JsonSerializerOptionsWeb); + var tools = result?.Tools ?? new List(); + + // Cache tool names for validation + _validToolNames = tools.Select(t => t.Name).ToList(); + _toolDefinitionsLoaded = true; + + _mcpLogger.Debug(LogSource, $"Tool definitions loaded successfully. _toolDefinitionsLoaded={_toolDefinitionsLoaded}, Tool count={tools.Count}, Instance={GetHashCode()}"); + + return tools; + } + catch (HttpRequestException ex) + { + throw CreateHttpExceptionWithInner(ex, "Network error fetching tool definitions"); + } + catch (TaskCanceledException ex) + { + throw CreateHttpExceptionWithInner(ex, "Timeout fetching tool definitions"); + } + catch (JsonException ex) + { + throw CreateHttpExceptionWithInner(ex, "JSON parsing error"); + } + catch (CliUsageException) + { + // Already sanitized, rethrow as-is + throw; + } + catch (Exception ex) + { + throw CreateHttpExceptionWithInner(ex, "Unexpected error fetching tool definitions"); + } + } + + private async Task GetMcpServerUrlAsync() + { + return await _cachedServerUrlLazy.Value; + } + + private async Task GetMcpServerUrlInternalAsync() + { + // Check config file + if (File.Exists(CliPaths.McpConfig)) + { + try + { + var json = await FileHelper.ReadAllTextAsync(CliPaths.McpConfig); + var config = JsonSerializer.Deserialize(json, JsonSerializerOptionsWeb); + if (!string.IsNullOrWhiteSpace(config?.ServerUrl)) + { + return config.ServerUrl.TrimEnd('/'); + } + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Failed to read MCP config file"); + } + } + + // Return default + return CliConsts.DefaultMcpServerUrl; + } + + private string CreateErrorResponse(string errorMessage) + { + return JsonSerializer.Serialize(new + { + content = new[] + { + new + { + type = "text", + text = errorMessage + } + }, + isError = true + }, JsonSerializerOptionsWeb); + } + + private string GetSanitizedHttpErrorMessage(HttpStatusCode statusCode) + { + return statusCode switch + { + HttpStatusCode.Unauthorized => "Authentication failed. Please ensure you are logged in with a valid account.", + HttpStatusCode.Forbidden => "Access denied. You do not have permission to use this tool.", + HttpStatusCode.NotFound => "The requested tool could not be found. It may have been removed or is temporarily unavailable.", + HttpStatusCode.BadRequest => "The tool request was invalid. Please check your input parameters and try again.", + (HttpStatusCode)429 => "Rate limit exceeded. Please wait a moment before trying again.", // TooManyRequests not available in .NET Standard 2.0 + HttpStatusCode.ServiceUnavailable => "The service is temporarily unavailable. Please try again later.", + HttpStatusCode.InternalServerError => "The tool execution encountered an internal error. Please try again later.", + _ => "The tool execution failed. Please try again later." + }; + } + + private CliUsageException CreateHttpExceptionWithInner(Exception ex, string context) + { + _mcpLogger.Error(LogSource, context, ex); + + var userMessage = ex switch + { + HttpRequestException => "Network connectivity issue. Please check your internet connection and try again.", + TaskCanceledException => "Request timed out. Please try again.", + JsonException => "Invalid response format received.", + _ => "An unexpected error occurred. Please try again later." + }; + + return new CliUsageException($"Failed to fetch tool definitions: {userMessage}", ex); + } + + private static class ErrorMessages + { + public const string NetworkConnectivity = "The tool execution failed due to a network connectivity issue. Please check your internet connection and try again."; + public const string Timeout = "The tool execution timed out. The operation took too long to complete. Please try again."; + public const string Unexpected = "The tool execution failed due to an unexpected error. Please try again later."; + } + + private class McpConfig + { + public string ServerUrl { get; set; } + } + + private class McpToolsResponse + { + public List Tools { get; set; } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/McpLogger.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/McpLogger.cs new file mode 100644 index 0000000000..20d6fce469 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/McpLogger.cs @@ -0,0 +1,150 @@ +using System; +using Microsoft.Extensions.Logging; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.Commands.Services; + +/// +/// MCP logger implementation that writes to both file (via Serilog) and stderr. +/// - All logs at or above the configured level are written to file via ILogger +/// - Warning and Error logs are also written to stderr +/// - Log level is controlled via ABP_MCP_LOG_LEVEL environment variable +/// +public class McpLogger : IMcpLogger, ISingletonDependency +{ + private const string LogPrefix = "[MCP]"; + + private readonly ILogger _logger; + private readonly McpLogLevel _configuredLogLevel; + + public McpLogger(ILogger logger) + { + _logger = logger; + _configuredLogLevel = GetConfiguredLogLevel(); + } + + public void Debug(string source, string message) + { + Log(McpLogLevel.Debug, source, message); + } + + public void Info(string source, string message) + { + Log(McpLogLevel.Info, source, message); + } + + public void Warning(string source, string message) + { + Log(McpLogLevel.Warning, source, message); + } + + public void Error(string source, string message) + { + Log(McpLogLevel.Error, source, message); + } + + public void Error(string source, string message, Exception exception) + { +#if DEBUG + var fullMessage = $"{message} | Exception: {exception.GetType().Name}: {exception.Message}"; +#else + var fullMessage = $"{message} | Exception: {exception.GetType().Name}"; +#endif + Log(McpLogLevel.Error, source, fullMessage); + } + + private void Log(McpLogLevel level, string source, string message) + { + if (_configuredLogLevel == McpLogLevel.None || level < _configuredLogLevel) + { + return; + } + + var mcpFormattedMessage = $"{LogPrefix}[{source}] {message}"; + + // File logging via Serilog + switch (level) + { + case McpLogLevel.Debug: + _logger.LogDebug(mcpFormattedMessage); + break; + case McpLogLevel.Info: + _logger.LogInformation(mcpFormattedMessage); + break; + case McpLogLevel.Warning: + _logger.LogWarning(mcpFormattedMessage); + break; + case McpLogLevel.Error: + _logger.LogError(mcpFormattedMessage); + break; + } + + // Stderr output for MCP protocol (Warning/Error only) + if (level >= McpLogLevel.Warning) + { + WriteToStderr(level.ToString().ToUpperInvariant(), message); + } + } + + private void WriteToStderr(string level, string message) + { + try + { + // Use synchronous write to avoid async issues in MCP context + Console.Error.WriteLine($"{LogPrefix}[{level}] {message}"); + } + catch + { + // Silently ignore stderr write errors + } + } + + private static McpLogLevel GetConfiguredLogLevel() + { + var envValue = Environment.GetEnvironmentVariable(CliConsts.McpLogLevelEnvironmentVariable); + var isEmpty = string.IsNullOrWhiteSpace(envValue); + +#if DEBUG + // In development builds, allow full control via environment variable + if (isEmpty) + { + return McpLogLevel.Info; // Default level + } + + return ParseLogLevel(envValue, allowDebug: true); +#else + // In release builds, restrict to Warning or higher (ignore env variable for Debug/Info) + if (isEmpty) + { + return McpLogLevel.Info; // Default level + } + + return ParseLogLevel(envValue, allowDebug: false); +#endif + } + + private static McpLogLevel ParseLogLevel(string value, bool allowDebug) + { + return value.ToLowerInvariant() switch + { + "debug" => allowDebug ? McpLogLevel.Debug : McpLogLevel.Info, + "info" => McpLogLevel.Info, + "warning" => McpLogLevel.Warning, + "error" => McpLogLevel.Error, + "none" => McpLogLevel.None, + _ => McpLogLevel.Info + }; + } +} + +/// +/// Log levels for MCP logging. +/// +public enum McpLogLevel +{ + Debug = 0, + Info = 1, + Warning = 2, + Error = 3, + None = 4 +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/McpServerService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/McpServerService.cs new file mode 100644 index 0000000000..01c48a1345 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/McpServerService.cs @@ -0,0 +1,181 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using ModelContextProtocol.Protocol; +using ModelContextProtocol.Server; +using Volo.Abp.Cli.Commands.Models; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Cli.Commands.Services; + +public class McpServerService : ITransientDependency +{ + private const string LogSource = nameof(McpServerService); + private const int MaxLogResponseLength = 500; + + private static readonly JsonSerializerOptions JsonCamelCaseOptions = new() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + + private static class ToolErrorMessages + { + public const string InvalidResponseFormat = "The tool execution completed but returned an invalid response format. Please try again."; + public const string UnexpectedError = "The tool execution failed due to an unexpected error. Please try again later."; + } + + private readonly McpHttpClientService _mcpHttpClient; + private readonly McpToolsCacheService _toolsCacheService; + private readonly IMcpLogger _mcpLogger; + + public McpServerService( + McpHttpClientService mcpHttpClient, + McpToolsCacheService toolsCacheService, + IMcpLogger mcpLogger) + { + _mcpHttpClient = mcpHttpClient; + _toolsCacheService = toolsCacheService; + _mcpLogger = mcpLogger; + } + + public async Task RunAsync(CancellationToken cancellationToken = default) + { + _mcpLogger.Info(LogSource, "Starting ABP MCP Server (stdio)"); + + var options = new McpServerOptions(); + + await RegisterAllToolsAsync(options); + + // Use NullLoggerFactory to prevent ModelContextProtocol library from logging to stdout + // All our logging goes to file and stderr via IMcpLogger + var server = McpServer.Create( + new StdioServerTransport("abp-mcp-server", NullLoggerFactory.Instance), + options + ); + + await server.RunAsync(cancellationToken); + + _mcpLogger.Info(LogSource, "ABP MCP Server stopped"); + } + + private async Task RegisterAllToolsAsync(McpServerOptions options) + { + // Get tool definitions from cache (or fetch from server) + var toolDefinitions = await _toolsCacheService.GetToolDefinitionsAsync(); + + _mcpLogger.Info(LogSource, $"Registering {toolDefinitions.Count} tools"); + + // Register each tool dynamically + foreach (var toolDef in toolDefinitions) + { + RegisterToolFromDefinition(options, toolDef); + } + } + + private void RegisterToolFromDefinition(McpServerOptions options, McpToolDefinition toolDef) + { + var inputSchema = toolDef.InputSchema ?? new McpToolInputSchema(); + RegisterTool(options, toolDef.Name, toolDef.Description, inputSchema, toolDef.OutputSchema); + } + + private static CallToolResult CreateErrorResult(string errorMessage) + { + return new CallToolResult + { + Content = new List + { + new TextContentBlock + { + Text = errorMessage + } + }, + IsError = true + }; + } + + private void RegisterTool( + McpServerOptions options, + string name, + string description, + object inputSchema, + JsonElement? outputSchema) + { + if (options.ToolCollection == null) + { + options.ToolCollection = new McpServerPrimitiveCollection(); + } + + var tool = new AbpMcpServerTool( + name, + description, + JsonSerializer.SerializeToElement(inputSchema, JsonCamelCaseOptions), + outputSchema, + (context, cancellationToken) => HandleToolInvocationAsync(name, context, cancellationToken) + ); + + options.ToolCollection.Add(tool); + } + + private async ValueTask HandleToolInvocationAsync( + string toolName, + RequestContext context, + CancellationToken cancellationToken) + { + _mcpLogger.Debug(LogSource, $"Tool '{toolName}' called with arguments: {context.Params.Arguments}"); + + try + { + var argumentsJson = JsonSerializer.SerializeToElement(context.Params.Arguments); + var resultJson = await _mcpHttpClient.CallToolAsync(toolName, argumentsJson); + + var callToolResult = TryDeserializeResult(resultJson, toolName); + if (callToolResult != null) + { + LogToolResult(toolName, callToolResult); + return callToolResult; + } + + return CreateErrorResult(ToolErrorMessages.InvalidResponseFormat); + } + catch (Exception ex) + { + _mcpLogger.Error(LogSource, $"Tool '{toolName}' execution failed '{ex.Message}'", ex); + return CreateErrorResult(ToolErrorMessages.UnexpectedError); + } + } + + private CallToolResult TryDeserializeResult(string resultJson, string toolName) + { + try + { + return JsonSerializer.Deserialize(resultJson); + } + catch (Exception ex) + { + _mcpLogger.Error(LogSource, $"Failed to deserialize response as CallToolResult: {ex.Message}"); + + var logResponse = resultJson.Length <= MaxLogResponseLength + ? resultJson + : resultJson.Substring(0, MaxLogResponseLength); + _mcpLogger.Debug(LogSource, $"Response was: {logResponse}"); + + return null; + } + } + + private void LogToolResult(string toolName, CallToolResult result) + { + if (result.IsError == true) + { + _mcpLogger.Warning(LogSource, $"Tool '{toolName}' returned an error"); + } + else + { + _mcpLogger.Debug(LogSource, $"Tool '{toolName}' executed successfully"); + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/McpToolsCacheService.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/McpToolsCacheService.cs new file mode 100644 index 0000000000..146f5e4ba9 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/Services/McpToolsCacheService.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Runtime.InteropServices; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Volo.Abp.Cli.Commands.Models; +using Volo.Abp.Cli.Memory; +using Volo.Abp.DependencyInjection; +using Volo.Abp.IO; + +namespace Volo.Abp.Cli.Commands.Services; + +public class McpToolsCacheService : ITransientDependency +{ + private const string LogSource = nameof(McpToolsCacheService); + private const int CacheValidityHours = 24; + + private readonly McpHttpClientService _mcpHttpClient; + private readonly MemoryService _memoryService; + private readonly ILogger _logger; + private readonly IMcpLogger _mcpLogger; + + public McpToolsCacheService( + McpHttpClientService mcpHttpClient, + MemoryService memoryService, + ILogger logger, + IMcpLogger mcpLogger) + { + _mcpHttpClient = mcpHttpClient; + _memoryService = memoryService; + _logger = logger; + _mcpLogger = mcpLogger; + } + + public async Task> GetToolDefinitionsAsync() + { + if (await IsCacheValidAsync()) + { + var cachedTools = await LoadFromCacheAsync(); + if (cachedTools != null) + { + _mcpLogger.Debug(LogSource, "Using cached tool definitions"); + // Initialize the HTTP client's tool names list from cache + _mcpHttpClient.InitializeToolNames(cachedTools); + return cachedTools; + } + } + + // Cache is invalid or missing, fetch from server + _mcpLogger.Info(LogSource, "Fetching tool definitions from server..."); + var tools = await _mcpHttpClient.GetToolDefinitionsAsync(); + + // Validate that we got tools + if (tools == null || tools.Count == 0) + { + throw new CliUsageException( + "Failed to fetch tool definitions from ABP.IO MCP Server. " + + "No tools available. The MCP server cannot start without tool definitions."); + } + + // Save tools to cache + await SaveToCacheAsync(tools); + await _memoryService.SetAsync(CliConsts.MemoryKeys.McpToolsLastFetchDate, DateTime.Now.ToString(CultureInfo.InvariantCulture)); + + _mcpLogger.Info(LogSource, $"Successfully fetched and cached {tools.Count} tool definitions"); + return tools; + } + + private async Task IsCacheValidAsync() + { + try + { + // Check if cache file exists + if (!File.Exists(CliPaths.McpToolsCache)) + { + return false; + } + + // Check timestamp in memory + var lastFetchTimeString = await _memoryService.GetAsync(CliConsts.MemoryKeys.McpToolsLastFetchDate); + if (string.IsNullOrEmpty(lastFetchTimeString)) + { + return false; + } + + if (DateTime.TryParse(lastFetchTimeString, CultureInfo.InvariantCulture, DateTimeStyles.None, out var lastFetchTime)) + { + // Check if less than configured hours old + if (DateTime.Now.Subtract(lastFetchTime).TotalHours < CacheValidityHours) + { + return true; + } + } + + return false; + } + catch (Exception ex) + { + _logger.LogWarning($"Error checking cache validity: {ex.Message}"); + return false; + } + } + + private async Task> LoadFromCacheAsync() + { + try + { + if (!File.Exists(CliPaths.McpToolsCache)) + { + return null; + } + + var json = await FileHelper.ReadAllTextAsync(CliPaths.McpToolsCache); + var tools = JsonSerializer.Deserialize>(json, new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }); + + return tools; + } + catch (Exception ex) + { + _logger.LogWarning($"Error loading cached tool definitions: {ex.Message}"); + return null; + } + } + + private Task SaveToCacheAsync(List tools) + { + try + { + // Ensure directory exists + var directory = Path.GetDirectoryName(CliPaths.McpToolsCache); + if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + + var json = JsonSerializer.Serialize(tools, new JsonSerializerOptions + { + WriteIndented = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }); + + // Using synchronous File.WriteAllText is acceptable here since cache writes are not on the critical path + // and we need to support multiple target frameworks + File.WriteAllText(CliPaths.McpToolsCache, json); + + // Set restrictive file permissions (user read/write only) + SetRestrictiveFilePermissions(CliPaths.McpToolsCache); + } + catch (Exception ex) + { + _logger.LogWarning($"Error saving tool definitions to cache: {ex.Message}"); + } + + return Task.CompletedTask; + } + + private void SetRestrictiveFilePermissions(string filePath) + { + try + { + // On Unix systems, set permissions to 600 (user read/write only) + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { +#if NET6_0_OR_GREATER + File.SetUnixFileMode(filePath, UnixFileMode.UserRead | UnixFileMode.UserWrite); +#endif + } + // On Windows, the file inherits permissions from the user profile directory, + // which is already restrictive to the current user + } + catch (Exception ex) + { + _logger.LogWarning($"Error setting file permissions: {ex.Message}"); + } + } +} + diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/DerivedClassFinder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/DerivedClassFinder.cs index 5462f3e9fb..b8f1d77bc8 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/DerivedClassFinder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/DerivedClassFinder.cs @@ -26,10 +26,10 @@ public class DerivedClassFinder : ITransientDependency var binFile = Path.Combine(csprojFileDirectory, "bin"); var objFile = Path.Combine(csprojFileDirectory, "obj"); - var csFiles = new DirectoryInfo(csprojFileDirectory) .GetFiles("*.cs", SearchOption.AllDirectories) - .Where(f => f.DirectoryName != null && (!f.DirectoryName.StartsWith(binFile) || !f.DirectoryName.StartsWith(objFile))) + .Where(f => !f.FullName.StartsWith(binFile, StringComparison.OrdinalIgnoreCase) && + !f.FullName.StartsWith(objFile, StringComparison.OrdinalIgnoreCase)) .Select(f => f.FullName) .ToList(); @@ -53,7 +53,13 @@ public class DerivedClassFinder : ITransientDependency protected bool IsDerived(string csFile, string baseClass) { - var root = CSharpSyntaxTree.ParseText(File.ReadAllText(csFile)).GetRoot(); + var csFileText = File.ReadAllText(csFile); + if (!csFileText.Contains("class")) + { + return false; + } + + var root = CSharpSyntaxTree.ParseText(csFileText).GetRoot(); var namespaceSyntax = root.DescendantNodes().OfType().FirstOrDefault(); var classDeclaration = (namespaceSyntax?.DescendantNodes().OfType())?.FirstOrDefault(); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/EfCoreMigrationManager.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/EfCoreMigrationManager.cs index ffa6a50edf..c4f1181fa3 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/EfCoreMigrationManager.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/EfCoreMigrationManager.cs @@ -44,13 +44,20 @@ public class EfCoreMigrationManager : ITransientDependency string dbContext, string outputDirectory) { + CmdHelper.RunCmd($"dotnet build", workingDirectory: dbMigrationsProjectFolder, exitCode: out var buildExitCode); + if (buildExitCode != 0) + { + Logger.LogWarning("Dotnet build failed for project folder {ProjectFolder}. Skipping EF Core migration command.", dbMigrationsProjectFolder); + return; + } + var dbContextOption = string.IsNullOrWhiteSpace(dbContext) ? string.Empty : $"--context {dbContext}"; CmdHelper.RunCmd($"dotnet ef migrations add {migrationName}" + $" --output-dir {outputDirectory}" + - $" {dbContextOption}", + $" {dbContextOption}", workingDirectory: dbMigrationsProjectFolder); } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionFileModifier.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionFileModifier.cs index 7a815df81d..c8d9127a2c 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionFileModifier.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectModification/SolutionFileModifier.cs @@ -18,16 +18,17 @@ public class SolutionFileModifier : ITransientDependency { _cmdHelper = cmdHelper; } - + public async Task RemoveProjectFromSolutionFileAsync(string solutionFile, string projectName) { - var list = _cmdHelper.RunCmdAndGetOutput($"dotnet sln \"{solutionFile}\" list"); + var workingDirectory = Path.GetDirectoryName(solutionFile); + var list = _cmdHelper.RunCmdAndGetOutput($"dotnet sln \"{solutionFile}\" list", workingDirectory: workingDirectory); foreach (var line in list.Split(new[] { Environment.NewLine, "\n" }, StringSplitOptions.None)) { if (Path.GetFileNameWithoutExtension(line.Trim()).Equals(projectName, StringComparison.InvariantCultureIgnoreCase)) { - _cmdHelper.RunCmd($"dotnet sln \"{solutionFile}\" remove \"{line.Trim()}\""); + _cmdHelper.RunCmd($"dotnet sln \"{solutionFile}\" remove \"{line.Trim()}\"", workingDirectory: workingDirectory); break; } } @@ -50,30 +51,27 @@ public class SolutionFileModifier : ITransientDependency private async Task AddModuleAsync(ModuleWithMastersInfo module, string solutionFile) { + var slnDir = Path.GetDirectoryName(solutionFile); var projectsUnderModule = Directory.GetFiles( - Path.Combine(Path.GetDirectoryName(solutionFile), "modules", module.Name), + Path.Combine(slnDir, "modules", module.Name), "*.csproj", SearchOption.AllDirectories); - + var projectsUnderTest = new List(); - if (Directory.Exists(Path.Combine(Path.GetDirectoryName(solutionFile), "modules", module.Name, "test"))) + if (Directory.Exists(Path.Combine(slnDir, "modules", module.Name, "test"))) { projectsUnderTest = Directory.GetFiles( - Path.Combine(Path.GetDirectoryName(solutionFile), "modules", module.Name, "test"), + Path.Combine(slnDir, "modules", module.Name, "test"), "*.csproj", SearchOption.AllDirectories).ToList(); } foreach (var projectPath in projectsUnderModule) { - var folder = projectsUnderTest.Contains(projectPath) ? "test" : "src"; - - var projectId = Path.GetFileName(projectPath).Replace(".csproj", ""); - var package = @$"modules\{module.Name}\{folder}\{projectId}\{projectId}.csproj"; - - _cmdHelper.RunCmd($"dotnet sln \"{solutionFile}\" add \"{package}\" --solution-folder {folder}"); + var solutionFolder = projectsUnderTest.Contains(projectPath) ? Path.Combine("test", module.Name) : Path.Combine("modules", module.Name); + _cmdHelper.RunCmd($"dotnet sln \"{solutionFile}\" add \"{projectPath}\" --solution-folder \"{solutionFolder}\"", workingDirectory: slnDir); } - + if (module.MasterModuleInfos != null) { foreach (var masterModule in module.MasterModuleInfos) diff --git a/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Program.cs b/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Program.cs index 61f2ef9de8..54d617c143 100644 --- a/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Program.cs +++ b/framework/src/Volo.Abp.Cli/Volo/Abp/Cli/Program.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.Extensions.DependencyInjection; using Serilog; using Serilog.Events; @@ -15,7 +15,7 @@ public class Program Console.OutputEncoding = System.Text.Encoding.UTF8; var loggerOutputTemplate = "{Message:lj}{NewLine}{Exception}"; - Log.Logger = new LoggerConfiguration() + var config = new LoggerConfiguration() .MinimumLevel.Information() .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) .MinimumLevel.Override("Volo.Abp", LogEventLevel.Warning) @@ -26,10 +26,21 @@ public class Program #else .MinimumLevel.Override("Volo.Abp.Cli", LogEventLevel.Information) #endif - .Enrich.FromLogContext() - .WriteTo.File(Path.Combine(CliPaths.Log, "abp-cli-logs.txt"), outputTemplate: loggerOutputTemplate) - .WriteTo.Console(theme: AnsiConsoleTheme.Sixteen, outputTemplate: loggerOutputTemplate) - .CreateLogger(); + .Enrich.FromLogContext(); + + if (args.Length > 0 && args[0].Equals("mcp", StringComparison.OrdinalIgnoreCase)) + { + Log.Logger = config + .WriteTo.File(Path.Combine(CliPaths.Log, "abp-cli-mcp-logs.txt"), outputTemplate: loggerOutputTemplate) + .CreateLogger(); + } + else + { + Log.Logger = config + .WriteTo.File(Path.Combine(CliPaths.Log, "abp-cli-logs.txt"), outputTemplate: loggerOutputTemplate) + .WriteTo.Console(theme: AnsiConsoleTheme.Sixteen, outputTemplate: loggerOutputTemplate) + .CreateLogger(); + } using (var application = AbpApplicationFactory.Create( options => diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/ActivityNameConsts.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/ActivityNameConsts.cs index 64b22ef78f..103f6afd63 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/ActivityNameConsts.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/Constants/ActivityNameConsts.cs @@ -1,4 +1,4 @@ -namespace Volo.Abp.Internal.Telemetry.Constants; +namespace Volo.Abp.Internal.Telemetry.Constants; public static class ActivityNameConsts { @@ -68,6 +68,7 @@ public static class ActivityNameConsts public const string AbpCliCommandsInstallModule = "AbpCli.Comands.InstallModule"; public const string AbpCliCommandsInstallLocalModule = "AbpCli.Comands.InstallLocalModule"; public const string AbpCliCommandsListModules = "AbpCli.Comands.ListModules"; + public const string AbpCliCommandsMcp = "AbpCli.Commands.Mcp"; public const string AbpCliRun = "AbpCli.Run"; public const string AbpCliExit = "AbpCli.Exit"; public const string ApplicationRun = "Application.Run"; diff --git a/modules/account/src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.abppkg.analyze.json b/modules/account/src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.abppkg.analyze.json index 26b2fd35ee..92c808c088 100644 --- a/modules/account/src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.abppkg.analyze.json +++ b/modules/account/src/Volo.Abp.Account.Application/Volo.Abp.Account.Application.abppkg.analyze.json @@ -412,6 +412,28 @@ "contentType": "applicationService", "name": "ProfileAppService", "summary": null + }, + { + "defaultValue": "true", + "displayName": "Is self-registration enabled", + "description": "Whether a user can register the account by him or herself.", + "isVisibleToClient": true, + "isInherited": true, + "isEncrypted": false, + "contentType": "setting", + "name": "Abp.Account.IsSelfRegistrationEnabled", + "summary": null + }, + { + "defaultValue": "true", + "displayName": "Authenticate with a local account", + "description": "Indicates if the server will allow users to authenticate with a local account.", + "isVisibleToClient": true, + "isInherited": true, + "isEncrypted": false, + "contentType": "setting", + "name": "Abp.Account.EnableLocalLogin", + "summary": null } ] } \ No newline at end of file diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/package.json b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/package.json index 21a4374fba..e372a4405b 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/package.json +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/package.json @@ -3,8 +3,8 @@ "name": "asp.net", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.shared": "~10.1.0-rc.1", - "@abp/prismjs": "~10.1.0-rc.1", - "@abp/highlight.js": "~10.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.shared": "~10.1.0-rc.2", + "@abp/prismjs": "~10.1.0-rc.2", + "@abp/highlight.js": "~10.1.0-rc.2" } } diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/yarn.lock b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/yarn.lock index 090a72c506..07e9b7f188 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/yarn.lock +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/yarn.lock @@ -2,203 +2,203 @@ # yarn lockfile v1 -"@abp/aspnetcore.mvc.ui.theme.shared@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.1.0-rc.1.tgz#7c0f721bdd4ec99f441a49e662a457d98c2c66fa" - integrity sha512-dB6wmR3C43Vn1/hiWY3IkxivUEWTGhO4mj3hffwAeyJqnEUh6kQUW6UfmYNGEhahSjhVIS3lUZ4x+R+yGCpemw== - dependencies: - "@abp/aspnetcore.mvc.ui" "~10.1.0-rc.1" - "@abp/bootstrap" "~10.1.0-rc.1" - "@abp/bootstrap-datepicker" "~10.1.0-rc.1" - "@abp/bootstrap-daterangepicker" "~10.1.0-rc.1" - "@abp/datatables.net-bs5" "~10.1.0-rc.1" - "@abp/font-awesome" "~10.1.0-rc.1" - "@abp/jquery-form" "~10.1.0-rc.1" - "@abp/jquery-validation-unobtrusive" "~10.1.0-rc.1" - "@abp/lodash" "~10.1.0-rc.1" - "@abp/luxon" "~10.1.0-rc.1" - "@abp/malihu-custom-scrollbar-plugin" "~10.1.0-rc.1" - "@abp/moment" "~10.1.0-rc.1" - "@abp/select2" "~10.1.0-rc.1" - "@abp/sweetalert2" "~10.1.0-rc.1" - "@abp/timeago" "~10.1.0-rc.1" - -"@abp/aspnetcore.mvc.ui@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.1.0-rc.1.tgz#290297353fdc15826a870b35162a0bbf4c9bc948" - integrity sha512-RBR5wPf5ygzuSpfXG5MeOGd/YOtQH5pVyU65T4DMtGpLKHQxgxbh7JK1btyC5zGmzr/Ds5+5Mcd7S66dIckDJQ== +"@abp/aspnetcore.mvc.ui.theme.shared@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.1.0-rc.2.tgz#e5056e4e159f5815e3cffecab5c46f3d7d4f79d7" + integrity sha512-bo56XzQZPYL/3ckWTTTSSUsSFSFJobvfE29cz13NIrZ/tBtWyQCAJn92wYHuY+6IezYUWb4ga3PkFeHRzR142A== + dependencies: + "@abp/aspnetcore.mvc.ui" "~10.1.0-rc.2" + "@abp/bootstrap" "~10.1.0-rc.2" + "@abp/bootstrap-datepicker" "~10.1.0-rc.2" + "@abp/bootstrap-daterangepicker" "~10.1.0-rc.2" + "@abp/datatables.net-bs5" "~10.1.0-rc.2" + "@abp/font-awesome" "~10.1.0-rc.2" + "@abp/jquery-form" "~10.1.0-rc.2" + "@abp/jquery-validation-unobtrusive" "~10.1.0-rc.2" + "@abp/lodash" "~10.1.0-rc.2" + "@abp/luxon" "~10.1.0-rc.2" + "@abp/malihu-custom-scrollbar-plugin" "~10.1.0-rc.2" + "@abp/moment" "~10.1.0-rc.2" + "@abp/select2" "~10.1.0-rc.2" + "@abp/sweetalert2" "~10.1.0-rc.2" + "@abp/timeago" "~10.1.0-rc.2" + +"@abp/aspnetcore.mvc.ui@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.1.0-rc.2.tgz#e25d3575d40bfcb3f809bd2d355671181ee5ff40" + integrity sha512-MOF86bVbi7N/nIla+361nsBrN4tiSka8xzpWcgqlLcCAl9ILG4rugbtafBAjN81taPma2peZM7egaOR4SDkTMw== dependencies: ansi-colors "^4.1.3" -"@abp/bootstrap-datepicker@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.1.0-rc.1.tgz#54730dc001dab746af18c41d51b73949742e3b2d" - integrity sha512-DhVfA9WjfRQrUwjN2eJTCjLEhAU7rLjWgMlifW1id3HbdMs9emKn0rzQqhZ8MOSUNBwLkbUWcXWnojRUFgUB+w== +"@abp/bootstrap-datepicker@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.1.0-rc.2.tgz#be80c6104ba53e18935fbf62ca2c1890f4b2fde4" + integrity sha512-BNcDYUSbZaLah4SfXm0efoqFTsOViVm6370k9L7vix/OGpIWwklJsr8y78lvdM5ANgNCfl0LPSq+seLJFc/OLA== dependencies: bootstrap-datepicker "^1.10.1" -"@abp/bootstrap-daterangepicker@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.1.0-rc.1.tgz#9af7ffe1d1bd4fc99e1606e30189e6f28bb2f201" - integrity sha512-/Hsge8UwLZFuPNWRZhpf4nVB4urPYsaKAuCz0pSH5OHmS4FWr0Ng3vnUd9fcSeaq9OV2HarcgGWDapYtszttVQ== +"@abp/bootstrap-daterangepicker@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.1.0-rc.2.tgz#f189f7d070ebd97d9cfdcb99571cab2d6a198ab5" + integrity sha512-bV8J0MuiAFVLkr48JsB6aZU6aPoqw+Gyhq1szQ74bEwNQlRBPuF92WVA5FACaUBj8dMUzR9HDDAYQuxUzpKYKA== dependencies: bootstrap-daterangepicker "^3.1.0" -"@abp/bootstrap@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.1.0-rc.1.tgz#e937f0a10874c0f3b0f719ff6a316b0ca91e65c5" - integrity sha512-GlRyH5oCjcmFk1eBGCHk6GglL2DJ5RLlDXn7HWU675u48DCQTkBbKP9qA3RcYERQB4qqhcOKEDtYPZVljRVHRQ== +"@abp/bootstrap@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.1.0-rc.2.tgz#2300800a29ea09b91f5ed2e6177e5921fe7d2a0f" + integrity sha512-K+tDI9vz/Y9B/yu0i3AVpm4v3Odi44Q/yH5hAprL7f4pGxEOiqAFB/qzHAxG+7Oa7wjv5tPLv+Cz4DavBQjd8Q== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" bootstrap "^5.3.8" -"@abp/clipboard@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/clipboard/-/clipboard-10.1.0-rc.1.tgz#ebb677e689372d5c0d07a5814b61eae45397a3c8" - integrity sha512-iJpKYQJy5mN1O8n0+4DsWoWKAOasLuUE9Cja01Yl7YTb6XwYCGozBAVZbi+YouhYmsSr2zM4ozKxXPbXXt0u1w== +"@abp/clipboard@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/clipboard/-/clipboard-10.1.0-rc.2.tgz#e99dbf190e3684e99c8e909bf38201c70e267502" + integrity sha512-kRS9pWc1jRgr4D4/EV9zdAy3rhhGBrcqk2as5+6Ih49npsEJY/cF5mYH7mj/ZYy8SHqtae/CR7bZsR+uCDKYrQ== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" clipboard "^2.0.11" -"@abp/core@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.1.0-rc.1.tgz#d9ce0958407fa39c8015eb2d9328a3a157aaa4f0" - integrity sha512-pFsaQei1W0JRJkhNdlxq+EDP7x+83nT0UHZetTHNzp8tkP4Cn8Ni/ZODIUku8ck1vcKc0X3pXKj42BN+yZ1g3g== +"@abp/core@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.1.0-rc.2.tgz#403687aff5a30788f7b7ca660abdfd85d89438aa" + integrity sha512-euuG2Hna/DT6/R1dGOjgp3vcehYtF+CcOkRj31oquYKaM5YWk4OaZ314DSpnjgs/xo8DuVc4eKFQwIxD9RK41w== dependencies: - "@abp/utils" "~10.1.0-rc.1" + "@abp/utils" "~10.1.0-rc.2" -"@abp/datatables.net-bs5@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.1.0-rc.1.tgz#7981bc64c543d24aa5b0ab373f5693187990d5be" - integrity sha512-yaYHuZ9eVHT2cZcH9r680X+imWhOqbhWlOlbLDpJtg7Un0ejDzou+JqxKYHHXmSEKYEPHjfCEhnXkMnyqvSUHQ== +"@abp/datatables.net-bs5@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.1.0-rc.2.tgz#a60650d1802b40751d30f8f6c56beb23fd66481b" + integrity sha512-IWwexNqbMpET54Fvm9LoPTJYf+4CoBbjFOvz3sL6CgO2feV5R5fKigjVU8zXKNh2W+RG8L6zEarfVxrr114TsA== dependencies: - "@abp/datatables.net" "~10.1.0-rc.1" + "@abp/datatables.net" "~10.1.0-rc.2" datatables.net-bs5 "^2.3.4" -"@abp/datatables.net@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.1.0-rc.1.tgz#f487357690060dd055dbb7f54e1298e8d8a81e40" - integrity sha512-KTKTW73H9vaX6J9ipXUf7d3QdseKaFRzl7e1QKldOoQxBs4pzT2NATGdUyiAnJsfPl4scpSFetzI66Rs7r3idg== +"@abp/datatables.net@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.1.0-rc.2.tgz#9147f68bc6dbc4eb40a9ddf65c7859e788cdcac2" + integrity sha512-a9DJpwg14S4nVOiC4ipw0CQwEYWB602e2gCJiH7W1mxopbQb135RxwhtdTnW//eIONcxC9IrEuvcBEAUVt2B7Q== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" datatables.net "^2.3.4" -"@abp/font-awesome@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.1.0-rc.1.tgz#2ee67b07ce6296c7f0ac5217c9d9677b9c70f05e" - integrity sha512-O/3oLbSu1pMNftQzQW29gSpkWCFJazFKjT7sOYId1Ueric0svyJ42hFr5gHf9RFVmCU5pgyhawcoTyzQ34PxYQ== +"@abp/font-awesome@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.1.0-rc.2.tgz#364466cfe67e41e0c4d16b57d3923d10f66369f1" + integrity sha512-F1Jy8xoFV2aA+VN+NH1gtrG96/j9w7Picc+KLoCoIyNnJr/xJur11XkJyu5ln8KF4V7p/DY7QaQodWV/btOs8g== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" "@fortawesome/fontawesome-free" "^7.0.1" -"@abp/highlight.js@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/highlight.js/-/highlight.js-10.1.0-rc.1.tgz#52667c1b0242b710d1b8ea65cf9c8dce50c2a213" - integrity sha512-LlcVW/R+WPy8DqWmCfkbxwsWVHdo4jqn1/wvcSuxtPYsQZcBLocM+IgmT1Be3Ti1gn5Gu2h89UsdYlDfLMllqQ== +"@abp/highlight.js@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/highlight.js/-/highlight.js-10.1.0-rc.2.tgz#6ad0e1ef9e49f0f90eba9d899fd72b6c30a4f9f0" + integrity sha512-jAX4p+i3secAaI3waXoMr7yoH6G1nWvcjR5UVin168H7I4UySxbF799T89v5tK8gtfWgaTjEydFZRypSQU/dHg== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" "@highlightjs/cdn-assets" "~11.11.1" -"@abp/jquery-form@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-10.1.0-rc.1.tgz#60fc98085671a2ff08e8c411847354b6a48cf55e" - integrity sha512-bj70hE7MsH2AoBb2yU2d6ng5Bo/wqjCNUI0kbG0T1T4yp1cToGAVJ6hrd+KOp2Tf6W+NITn1EycxRTCktE9QRA== +"@abp/jquery-form@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-10.1.0-rc.2.tgz#3857717d07569c22d4bbbe459238abeb816d606a" + integrity sha512-2D5WHVnfK9bhRces1tgPwOEoc7KCYKYiKHBOcqct+LTA7zoRjJv/PM8/JhFVl+grVIw1aSwO4tU3YfZ22Vxipg== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" jquery-form "^4.3.0" -"@abp/jquery-validation-unobtrusive@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.1.0-rc.1.tgz#a820989d8daf53096f838817cb2b3dba21f71653" - integrity sha512-7uXxbvuZ+ecD5suH3nx/ggiFAA8GjXGAB5tuGCdqePdBn1PMzDIDI1ZP9q8sR7sMjo7RKVK6MzX+PazUuJ4L7g== +"@abp/jquery-validation-unobtrusive@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.1.0-rc.2.tgz#efd7b69a078a20c0bf405408dbdf52a7bf770b3b" + integrity sha512-tZ0MWgzBqp+SNfMxM0z2cGB21NiTHuVJyyQaXKE/ptuD5pc0uRkcqw/J2kWfiqsoVgChz27IB6h8/jqDafS4qg== dependencies: - "@abp/jquery-validation" "~10.1.0-rc.1" + "@abp/jquery-validation" "~10.1.0-rc.2" jquery-validation-unobtrusive "^4.0.0" -"@abp/jquery-validation@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.1.0-rc.1.tgz#c8630b0a53ee93218be5c82264085b36b0b07936" - integrity sha512-/eS4vy5gwHIaRr6aHkvO6xWRjL22bVly7S0AqbZw2OxJ0pw1n23n+zipn/NDRSngRZSajTLLm1pYCG5qlJGd3A== +"@abp/jquery-validation@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.1.0-rc.2.tgz#d39537a7356c51f9db2e66f6740cf6df86bd0442" + integrity sha512-LOkS0NKk4pLtLjPU0CCbwROyUg6EtJN8Z/it7QuKK1CIRfYYcAStgNnNm5geZP7CqECIkoiFfgWjI+L5Z9/Tfg== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" jquery-validation "^1.21.0" -"@abp/jquery@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.1.0-rc.1.tgz#38d5d5b5a6144bf207773ef26f36d381a257a635" - integrity sha512-YarNlYPG7MfLc0fSnOcVrp9Da1EUxz4zUERnTOQ0/EdOUdcFhGAqI2yfoTFCxqX+TSphQWZEdif/tsfULtkI1A== +"@abp/jquery@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.1.0-rc.2.tgz#101a55f70d510978c8c05f5857d0e9d4965263f7" + integrity sha512-bQV1uFWGtwRYjNOsqJ8FM2004idX2Jj7YVL19YF1/PjyPUSMX+s8/IvJizBjyY5hPAiWBBhmV9g+IFWzxlDQoQ== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" jquery "~3.7.1" -"@abp/lodash@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.1.0-rc.1.tgz#bf735e34a42ddcdf19e331c8c5cf1a24a87475cb" - integrity sha512-JQV0oFLE90V9GdrD37PTMiOylw/U6yjpDu5PblVW/zlNmw/TzEtukp3/vJl7GJxoKRYWtgJf1nFLgG/+2kjE1A== +"@abp/lodash@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.1.0-rc.2.tgz#d08c03f8d3d0fbaa3e71e603cbe5fb7f176933ef" + integrity sha512-KCnD1p2y52ZI+2ifpiFIUAiDPsKehnOD8HV5qKeObO6UCP97okif8IP+sQDmNQb8O33y/NKTyx/HcpwBbe/NYQ== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" lodash "^4.17.21" -"@abp/luxon@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.1.0-rc.1.tgz#1c883ad3a5653f0c6ce4965e4b4ce21543129534" - integrity sha512-SDtwjIDR4OzrtzRWpgb8atUSK0DwajJ5LC486xHq5ftnbFETRfOwihyIfC2H4oIPPDE5oS+LOnjCkRYVkGt0oQ== +"@abp/luxon@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.1.0-rc.2.tgz#ef8d2b323bac054fc9610e241e1b1763d229e065" + integrity sha512-qYFl6XO3g9mZiu0dtIczI7LRuYWwc+RkpbDzSmruXcRks3KA+ZZco2vhHNnlwtXcINl/TXtbW7Wc0MX+8IB1Kw== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" luxon "^3.7.2" -"@abp/malihu-custom-scrollbar-plugin@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.1.0-rc.1.tgz#770106795f5eba9d36dd2d4002e450c215c66787" - integrity sha512-EFwQ7rUiPM6mxznn1t23ySzQH+VQ0dB2YxOqBAluFR/yC3qypjb98jGQaZKsJx9+iT4DuG9gGPKOfct1Sqh0KQ== +"@abp/malihu-custom-scrollbar-plugin@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.1.0-rc.2.tgz#dfaf666442c7c122f7da72c83b9adf194d5b6ec8" + integrity sha512-PudMHmNQgZ6JZeaVt1ZoXLqO0UZXJzUYiBah2LDkC4EMLjnMJFINHBoEVVa4ooXH0yjFv+zsbN0vWZYJ8TBJIA== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/moment@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.1.0-rc.1.tgz#fe5bfa5bc28bb847cf801523c1b2f118ffb12447" - integrity sha512-P6z9YO/FC/5DhTDWsHkquo5tEzIhSfUnRefLcZtv7XuJtevH/93NFSr23RL6D+F4r3cB5GMXGFUYmXGKWoXGHg== +"@abp/moment@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.1.0-rc.2.tgz#610a1592d13984aea51abbd13df8c5995a089149" + integrity sha512-ep8PnAXARw0t/wtGOVp/oiNhF3B0Bh6y2vRzKrcSoyXAQREGGm4fJdZVYZLGTfI4lFLTjebEgf4O7T9feUwJAw== dependencies: moment "^2.30.1" -"@abp/prismjs@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/prismjs/-/prismjs-10.1.0-rc.1.tgz#26ef6c368dbfdd27d2836f1dd8cd9e8cf630f0ab" - integrity sha512-6OpD/w7NOSI7OpjubMx45gXsZY5K6ggynJ9uSqv3NDxSbj9l6c8cRTX1i56ATT10rhyYxZIw83FRhMlxOiLfjg== +"@abp/prismjs@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/prismjs/-/prismjs-10.1.0-rc.2.tgz#8565bab503a16fc349f4b0fa2609ad412ff838be" + integrity sha512-SmZWMyJ3cJW+qj4CWJ7y2kD6PMx2zfZMA5X5jPunsytG4Eht4AVyIR38Y4QSpO62zZgkHyZlSTFOozBfhrlv9A== dependencies: - "@abp/clipboard" "~10.1.0-rc.1" - "@abp/core" "~10.1.0-rc.1" + "@abp/clipboard" "~10.1.0-rc.2" + "@abp/core" "~10.1.0-rc.2" prismjs "^1.30.0" -"@abp/select2@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.1.0-rc.1.tgz#19432bb6483f8a3470f02a3037612835b4c54ef4" - integrity sha512-xPvdrgXZzRhEIrEyw9eoITc+hYlbi8fSW3BGQ1ZMBXWgYkjWxhj/1AuLsu2ewFAKdBNz+PewjA0MKP6iBLATHw== +"@abp/select2@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.1.0-rc.2.tgz#40c5418d007fc36817eecbe6388d767e4e7ca887" + integrity sha512-Pq0wlpL01sWRLUg5um3JtBXIqi3mmbwPwvgxP8hFbQngAt9JXAK8geNRiTMrIZgtW/ycXtM1v6I4zuWOLOeAGg== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" select2 "^4.0.13" -"@abp/sweetalert2@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.1.0-rc.1.tgz#40f4d6964cd6620dcb81040d84e432f4ae314686" - integrity sha512-E4dUC2k/kClBnadtGK5NWR9YmCGgKq5uOGjEVtzXxJH62MlaCXlcxRrK9wNzX8pzDMDYFKjuOUugoROEgbRKWQ== +"@abp/sweetalert2@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.1.0-rc.2.tgz#d35858c69e10c6726b02cdfcea88dfc32385963f" + integrity sha512-s9VPRToohN45uzHcKCF5Mcj8FVjsXcXUb0U3tuaT/Y+u4adHB3fBxYiXJFM0sVsCJ81dFktxwka40Wm8Taz/zA== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" sweetalert2 "^11.23.0" -"@abp/timeago@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.1.0-rc.1.tgz#482e3a645f63ee9adf708ef8ae895fcf2bcb4271" - integrity sha512-UOb2o6bvi3+2kbD7+rUJdqTQvTbgqUD7ktd0U5Hulb3dBOyv6MZEagzMbXA003OROiS23JzzT7M0lLStvixwlg== +"@abp/timeago@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.1.0-rc.2.tgz#98d630cc3843eee64dbcc34fb8ca5afbab034718" + integrity sha512-vJmk+otyXXJE2s2J8iYpLVaFuNAYnIUSOitmi7umYnL+k/UE2KQhBXU7FR0/OBY9mAZYd+shaiGIU1LMSaJ+Xg== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" timeago "^1.6.7" -"@abp/utils@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.1.0-rc.1.tgz#e53ef81f39fd620b41ae2bfb69d06ee85ccb28ad" - integrity sha512-xBHIw5rmAttN5PL5C84L2znHkA2HMrP6HaA2aV5k6KU7BtOWJhviZnKJgbUBfufNQNeLwlThCHAfS7aOv/UQkg== +"@abp/utils@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.1.0-rc.2.tgz#86a980c6536b3b5ce185d406723b28be421864ac" + integrity sha512-Oz863VNA8fraQ81vTvqM0IqwiaseLwfFU5QNn6iOGOfn5wQrEkPwtZ0jMI+DGNtJgPzoKiq+iKc3K+SiuVgldg== dependencies: just-compare "^2.3.0" diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/package.json b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/package.json index 9c12be2aa6..a3ecabd4c4 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/package.json +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/package.json @@ -3,8 +3,8 @@ "name": "asp.net", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~10.1.0-rc.1", - "@abp/prismjs": "~10.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.basic": "~10.1.0-rc.2", + "@abp/prismjs": "~10.1.0-rc.2" }, "devDependencies": {} } diff --git a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/yarn.lock b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/yarn.lock index 891127c606..f29a796639 100644 --- a/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/yarn.lock +++ b/modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo/yarn.lock @@ -2,202 +2,202 @@ # yarn lockfile v1 -"@abp/aspnetcore.mvc.ui.theme.basic@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-10.1.0-rc.1.tgz#99407c4108b685eef91f21d038cc5bd2343b5847" - integrity sha512-UbVgYf1K+zUqueNCwOl4QoUAz7nwJyoQJSqq0gi7LA9JBRBbb8nDA8szQjAUVqF4h+k4o61588MUQnerUQlZ6g== - dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~10.1.0-rc.1" - -"@abp/aspnetcore.mvc.ui.theme.shared@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.1.0-rc.1.tgz#7c0f721bdd4ec99f441a49e662a457d98c2c66fa" - integrity sha512-dB6wmR3C43Vn1/hiWY3IkxivUEWTGhO4mj3hffwAeyJqnEUh6kQUW6UfmYNGEhahSjhVIS3lUZ4x+R+yGCpemw== - dependencies: - "@abp/aspnetcore.mvc.ui" "~10.1.0-rc.1" - "@abp/bootstrap" "~10.1.0-rc.1" - "@abp/bootstrap-datepicker" "~10.1.0-rc.1" - "@abp/bootstrap-daterangepicker" "~10.1.0-rc.1" - "@abp/datatables.net-bs5" "~10.1.0-rc.1" - "@abp/font-awesome" "~10.1.0-rc.1" - "@abp/jquery-form" "~10.1.0-rc.1" - "@abp/jquery-validation-unobtrusive" "~10.1.0-rc.1" - "@abp/lodash" "~10.1.0-rc.1" - "@abp/luxon" "~10.1.0-rc.1" - "@abp/malihu-custom-scrollbar-plugin" "~10.1.0-rc.1" - "@abp/moment" "~10.1.0-rc.1" - "@abp/select2" "~10.1.0-rc.1" - "@abp/sweetalert2" "~10.1.0-rc.1" - "@abp/timeago" "~10.1.0-rc.1" - -"@abp/aspnetcore.mvc.ui@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.1.0-rc.1.tgz#290297353fdc15826a870b35162a0bbf4c9bc948" - integrity sha512-RBR5wPf5ygzuSpfXG5MeOGd/YOtQH5pVyU65T4DMtGpLKHQxgxbh7JK1btyC5zGmzr/Ds5+5Mcd7S66dIckDJQ== +"@abp/aspnetcore.mvc.ui.theme.basic@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-10.1.0-rc.2.tgz#599f5c47a417d1230fc17c0446a0229f920f7246" + integrity sha512-8F4nEK+VtgRRf8n+66HMbtCEaOMCW/OdbSEWRl9ahMNoj860oPIJ8P8Qn/2+LjtkPMdDAfCdEzyDzCd3igaFaA== + dependencies: + "@abp/aspnetcore.mvc.ui.theme.shared" "~10.1.0-rc.2" + +"@abp/aspnetcore.mvc.ui.theme.shared@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.1.0-rc.2.tgz#e5056e4e159f5815e3cffecab5c46f3d7d4f79d7" + integrity sha512-bo56XzQZPYL/3ckWTTTSSUsSFSFJobvfE29cz13NIrZ/tBtWyQCAJn92wYHuY+6IezYUWb4ga3PkFeHRzR142A== + dependencies: + "@abp/aspnetcore.mvc.ui" "~10.1.0-rc.2" + "@abp/bootstrap" "~10.1.0-rc.2" + "@abp/bootstrap-datepicker" "~10.1.0-rc.2" + "@abp/bootstrap-daterangepicker" "~10.1.0-rc.2" + "@abp/datatables.net-bs5" "~10.1.0-rc.2" + "@abp/font-awesome" "~10.1.0-rc.2" + "@abp/jquery-form" "~10.1.0-rc.2" + "@abp/jquery-validation-unobtrusive" "~10.1.0-rc.2" + "@abp/lodash" "~10.1.0-rc.2" + "@abp/luxon" "~10.1.0-rc.2" + "@abp/malihu-custom-scrollbar-plugin" "~10.1.0-rc.2" + "@abp/moment" "~10.1.0-rc.2" + "@abp/select2" "~10.1.0-rc.2" + "@abp/sweetalert2" "~10.1.0-rc.2" + "@abp/timeago" "~10.1.0-rc.2" + +"@abp/aspnetcore.mvc.ui@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.1.0-rc.2.tgz#e25d3575d40bfcb3f809bd2d355671181ee5ff40" + integrity sha512-MOF86bVbi7N/nIla+361nsBrN4tiSka8xzpWcgqlLcCAl9ILG4rugbtafBAjN81taPma2peZM7egaOR4SDkTMw== dependencies: ansi-colors "^4.1.3" -"@abp/bootstrap-datepicker@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.1.0-rc.1.tgz#54730dc001dab746af18c41d51b73949742e3b2d" - integrity sha512-DhVfA9WjfRQrUwjN2eJTCjLEhAU7rLjWgMlifW1id3HbdMs9emKn0rzQqhZ8MOSUNBwLkbUWcXWnojRUFgUB+w== +"@abp/bootstrap-datepicker@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.1.0-rc.2.tgz#be80c6104ba53e18935fbf62ca2c1890f4b2fde4" + integrity sha512-BNcDYUSbZaLah4SfXm0efoqFTsOViVm6370k9L7vix/OGpIWwklJsr8y78lvdM5ANgNCfl0LPSq+seLJFc/OLA== dependencies: bootstrap-datepicker "^1.10.1" -"@abp/bootstrap-daterangepicker@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.1.0-rc.1.tgz#9af7ffe1d1bd4fc99e1606e30189e6f28bb2f201" - integrity sha512-/Hsge8UwLZFuPNWRZhpf4nVB4urPYsaKAuCz0pSH5OHmS4FWr0Ng3vnUd9fcSeaq9OV2HarcgGWDapYtszttVQ== +"@abp/bootstrap-daterangepicker@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.1.0-rc.2.tgz#f189f7d070ebd97d9cfdcb99571cab2d6a198ab5" + integrity sha512-bV8J0MuiAFVLkr48JsB6aZU6aPoqw+Gyhq1szQ74bEwNQlRBPuF92WVA5FACaUBj8dMUzR9HDDAYQuxUzpKYKA== dependencies: bootstrap-daterangepicker "^3.1.0" -"@abp/bootstrap@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.1.0-rc.1.tgz#e937f0a10874c0f3b0f719ff6a316b0ca91e65c5" - integrity sha512-GlRyH5oCjcmFk1eBGCHk6GglL2DJ5RLlDXn7HWU675u48DCQTkBbKP9qA3RcYERQB4qqhcOKEDtYPZVljRVHRQ== +"@abp/bootstrap@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.1.0-rc.2.tgz#2300800a29ea09b91f5ed2e6177e5921fe7d2a0f" + integrity sha512-K+tDI9vz/Y9B/yu0i3AVpm4v3Odi44Q/yH5hAprL7f4pGxEOiqAFB/qzHAxG+7Oa7wjv5tPLv+Cz4DavBQjd8Q== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" bootstrap "^5.3.8" -"@abp/clipboard@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/clipboard/-/clipboard-10.1.0-rc.1.tgz#ebb677e689372d5c0d07a5814b61eae45397a3c8" - integrity sha512-iJpKYQJy5mN1O8n0+4DsWoWKAOasLuUE9Cja01Yl7YTb6XwYCGozBAVZbi+YouhYmsSr2zM4ozKxXPbXXt0u1w== +"@abp/clipboard@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/clipboard/-/clipboard-10.1.0-rc.2.tgz#e99dbf190e3684e99c8e909bf38201c70e267502" + integrity sha512-kRS9pWc1jRgr4D4/EV9zdAy3rhhGBrcqk2as5+6Ih49npsEJY/cF5mYH7mj/ZYy8SHqtae/CR7bZsR+uCDKYrQ== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" clipboard "^2.0.11" -"@abp/core@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.1.0-rc.1.tgz#d9ce0958407fa39c8015eb2d9328a3a157aaa4f0" - integrity sha512-pFsaQei1W0JRJkhNdlxq+EDP7x+83nT0UHZetTHNzp8tkP4Cn8Ni/ZODIUku8ck1vcKc0X3pXKj42BN+yZ1g3g== +"@abp/core@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.1.0-rc.2.tgz#403687aff5a30788f7b7ca660abdfd85d89438aa" + integrity sha512-euuG2Hna/DT6/R1dGOjgp3vcehYtF+CcOkRj31oquYKaM5YWk4OaZ314DSpnjgs/xo8DuVc4eKFQwIxD9RK41w== dependencies: - "@abp/utils" "~10.1.0-rc.1" + "@abp/utils" "~10.1.0-rc.2" -"@abp/datatables.net-bs5@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.1.0-rc.1.tgz#7981bc64c543d24aa5b0ab373f5693187990d5be" - integrity sha512-yaYHuZ9eVHT2cZcH9r680X+imWhOqbhWlOlbLDpJtg7Un0ejDzou+JqxKYHHXmSEKYEPHjfCEhnXkMnyqvSUHQ== +"@abp/datatables.net-bs5@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.1.0-rc.2.tgz#a60650d1802b40751d30f8f6c56beb23fd66481b" + integrity sha512-IWwexNqbMpET54Fvm9LoPTJYf+4CoBbjFOvz3sL6CgO2feV5R5fKigjVU8zXKNh2W+RG8L6zEarfVxrr114TsA== dependencies: - "@abp/datatables.net" "~10.1.0-rc.1" + "@abp/datatables.net" "~10.1.0-rc.2" datatables.net-bs5 "^2.3.4" -"@abp/datatables.net@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.1.0-rc.1.tgz#f487357690060dd055dbb7f54e1298e8d8a81e40" - integrity sha512-KTKTW73H9vaX6J9ipXUf7d3QdseKaFRzl7e1QKldOoQxBs4pzT2NATGdUyiAnJsfPl4scpSFetzI66Rs7r3idg== +"@abp/datatables.net@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.1.0-rc.2.tgz#9147f68bc6dbc4eb40a9ddf65c7859e788cdcac2" + integrity sha512-a9DJpwg14S4nVOiC4ipw0CQwEYWB602e2gCJiH7W1mxopbQb135RxwhtdTnW//eIONcxC9IrEuvcBEAUVt2B7Q== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" datatables.net "^2.3.4" -"@abp/font-awesome@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.1.0-rc.1.tgz#2ee67b07ce6296c7f0ac5217c9d9677b9c70f05e" - integrity sha512-O/3oLbSu1pMNftQzQW29gSpkWCFJazFKjT7sOYId1Ueric0svyJ42hFr5gHf9RFVmCU5pgyhawcoTyzQ34PxYQ== +"@abp/font-awesome@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.1.0-rc.2.tgz#364466cfe67e41e0c4d16b57d3923d10f66369f1" + integrity sha512-F1Jy8xoFV2aA+VN+NH1gtrG96/j9w7Picc+KLoCoIyNnJr/xJur11XkJyu5ln8KF4V7p/DY7QaQodWV/btOs8g== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" "@fortawesome/fontawesome-free" "^7.0.1" -"@abp/jquery-form@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-10.1.0-rc.1.tgz#60fc98085671a2ff08e8c411847354b6a48cf55e" - integrity sha512-bj70hE7MsH2AoBb2yU2d6ng5Bo/wqjCNUI0kbG0T1T4yp1cToGAVJ6hrd+KOp2Tf6W+NITn1EycxRTCktE9QRA== +"@abp/jquery-form@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-10.1.0-rc.2.tgz#3857717d07569c22d4bbbe459238abeb816d606a" + integrity sha512-2D5WHVnfK9bhRces1tgPwOEoc7KCYKYiKHBOcqct+LTA7zoRjJv/PM8/JhFVl+grVIw1aSwO4tU3YfZ22Vxipg== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" jquery-form "^4.3.0" -"@abp/jquery-validation-unobtrusive@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.1.0-rc.1.tgz#a820989d8daf53096f838817cb2b3dba21f71653" - integrity sha512-7uXxbvuZ+ecD5suH3nx/ggiFAA8GjXGAB5tuGCdqePdBn1PMzDIDI1ZP9q8sR7sMjo7RKVK6MzX+PazUuJ4L7g== +"@abp/jquery-validation-unobtrusive@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.1.0-rc.2.tgz#efd7b69a078a20c0bf405408dbdf52a7bf770b3b" + integrity sha512-tZ0MWgzBqp+SNfMxM0z2cGB21NiTHuVJyyQaXKE/ptuD5pc0uRkcqw/J2kWfiqsoVgChz27IB6h8/jqDafS4qg== dependencies: - "@abp/jquery-validation" "~10.1.0-rc.1" + "@abp/jquery-validation" "~10.1.0-rc.2" jquery-validation-unobtrusive "^4.0.0" -"@abp/jquery-validation@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.1.0-rc.1.tgz#c8630b0a53ee93218be5c82264085b36b0b07936" - integrity sha512-/eS4vy5gwHIaRr6aHkvO6xWRjL22bVly7S0AqbZw2OxJ0pw1n23n+zipn/NDRSngRZSajTLLm1pYCG5qlJGd3A== +"@abp/jquery-validation@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.1.0-rc.2.tgz#d39537a7356c51f9db2e66f6740cf6df86bd0442" + integrity sha512-LOkS0NKk4pLtLjPU0CCbwROyUg6EtJN8Z/it7QuKK1CIRfYYcAStgNnNm5geZP7CqECIkoiFfgWjI+L5Z9/Tfg== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" jquery-validation "^1.21.0" -"@abp/jquery@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.1.0-rc.1.tgz#38d5d5b5a6144bf207773ef26f36d381a257a635" - integrity sha512-YarNlYPG7MfLc0fSnOcVrp9Da1EUxz4zUERnTOQ0/EdOUdcFhGAqI2yfoTFCxqX+TSphQWZEdif/tsfULtkI1A== +"@abp/jquery@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.1.0-rc.2.tgz#101a55f70d510978c8c05f5857d0e9d4965263f7" + integrity sha512-bQV1uFWGtwRYjNOsqJ8FM2004idX2Jj7YVL19YF1/PjyPUSMX+s8/IvJizBjyY5hPAiWBBhmV9g+IFWzxlDQoQ== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" jquery "~3.7.1" -"@abp/lodash@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.1.0-rc.1.tgz#bf735e34a42ddcdf19e331c8c5cf1a24a87475cb" - integrity sha512-JQV0oFLE90V9GdrD37PTMiOylw/U6yjpDu5PblVW/zlNmw/TzEtukp3/vJl7GJxoKRYWtgJf1nFLgG/+2kjE1A== +"@abp/lodash@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.1.0-rc.2.tgz#d08c03f8d3d0fbaa3e71e603cbe5fb7f176933ef" + integrity sha512-KCnD1p2y52ZI+2ifpiFIUAiDPsKehnOD8HV5qKeObO6UCP97okif8IP+sQDmNQb8O33y/NKTyx/HcpwBbe/NYQ== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" lodash "^4.17.21" -"@abp/luxon@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.1.0-rc.1.tgz#1c883ad3a5653f0c6ce4965e4b4ce21543129534" - integrity sha512-SDtwjIDR4OzrtzRWpgb8atUSK0DwajJ5LC486xHq5ftnbFETRfOwihyIfC2H4oIPPDE5oS+LOnjCkRYVkGt0oQ== +"@abp/luxon@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.1.0-rc.2.tgz#ef8d2b323bac054fc9610e241e1b1763d229e065" + integrity sha512-qYFl6XO3g9mZiu0dtIczI7LRuYWwc+RkpbDzSmruXcRks3KA+ZZco2vhHNnlwtXcINl/TXtbW7Wc0MX+8IB1Kw== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" luxon "^3.7.2" -"@abp/malihu-custom-scrollbar-plugin@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.1.0-rc.1.tgz#770106795f5eba9d36dd2d4002e450c215c66787" - integrity sha512-EFwQ7rUiPM6mxznn1t23ySzQH+VQ0dB2YxOqBAluFR/yC3qypjb98jGQaZKsJx9+iT4DuG9gGPKOfct1Sqh0KQ== +"@abp/malihu-custom-scrollbar-plugin@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.1.0-rc.2.tgz#dfaf666442c7c122f7da72c83b9adf194d5b6ec8" + integrity sha512-PudMHmNQgZ6JZeaVt1ZoXLqO0UZXJzUYiBah2LDkC4EMLjnMJFINHBoEVVa4ooXH0yjFv+zsbN0vWZYJ8TBJIA== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/moment@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.1.0-rc.1.tgz#fe5bfa5bc28bb847cf801523c1b2f118ffb12447" - integrity sha512-P6z9YO/FC/5DhTDWsHkquo5tEzIhSfUnRefLcZtv7XuJtevH/93NFSr23RL6D+F4r3cB5GMXGFUYmXGKWoXGHg== +"@abp/moment@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.1.0-rc.2.tgz#610a1592d13984aea51abbd13df8c5995a089149" + integrity sha512-ep8PnAXARw0t/wtGOVp/oiNhF3B0Bh6y2vRzKrcSoyXAQREGGm4fJdZVYZLGTfI4lFLTjebEgf4O7T9feUwJAw== dependencies: moment "^2.30.1" -"@abp/prismjs@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/prismjs/-/prismjs-10.1.0-rc.1.tgz#26ef6c368dbfdd27d2836f1dd8cd9e8cf630f0ab" - integrity sha512-6OpD/w7NOSI7OpjubMx45gXsZY5K6ggynJ9uSqv3NDxSbj9l6c8cRTX1i56ATT10rhyYxZIw83FRhMlxOiLfjg== +"@abp/prismjs@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/prismjs/-/prismjs-10.1.0-rc.2.tgz#8565bab503a16fc349f4b0fa2609ad412ff838be" + integrity sha512-SmZWMyJ3cJW+qj4CWJ7y2kD6PMx2zfZMA5X5jPunsytG4Eht4AVyIR38Y4QSpO62zZgkHyZlSTFOozBfhrlv9A== dependencies: - "@abp/clipboard" "~10.1.0-rc.1" - "@abp/core" "~10.1.0-rc.1" + "@abp/clipboard" "~10.1.0-rc.2" + "@abp/core" "~10.1.0-rc.2" prismjs "^1.30.0" -"@abp/select2@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.1.0-rc.1.tgz#19432bb6483f8a3470f02a3037612835b4c54ef4" - integrity sha512-xPvdrgXZzRhEIrEyw9eoITc+hYlbi8fSW3BGQ1ZMBXWgYkjWxhj/1AuLsu2ewFAKdBNz+PewjA0MKP6iBLATHw== +"@abp/select2@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.1.0-rc.2.tgz#40c5418d007fc36817eecbe6388d767e4e7ca887" + integrity sha512-Pq0wlpL01sWRLUg5um3JtBXIqi3mmbwPwvgxP8hFbQngAt9JXAK8geNRiTMrIZgtW/ycXtM1v6I4zuWOLOeAGg== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" select2 "^4.0.13" -"@abp/sweetalert2@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.1.0-rc.1.tgz#40f4d6964cd6620dcb81040d84e432f4ae314686" - integrity sha512-E4dUC2k/kClBnadtGK5NWR9YmCGgKq5uOGjEVtzXxJH62MlaCXlcxRrK9wNzX8pzDMDYFKjuOUugoROEgbRKWQ== +"@abp/sweetalert2@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.1.0-rc.2.tgz#d35858c69e10c6726b02cdfcea88dfc32385963f" + integrity sha512-s9VPRToohN45uzHcKCF5Mcj8FVjsXcXUb0U3tuaT/Y+u4adHB3fBxYiXJFM0sVsCJ81dFktxwka40Wm8Taz/zA== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" sweetalert2 "^11.23.0" -"@abp/timeago@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.1.0-rc.1.tgz#482e3a645f63ee9adf708ef8ae895fcf2bcb4271" - integrity sha512-UOb2o6bvi3+2kbD7+rUJdqTQvTbgqUD7ktd0U5Hulb3dBOyv6MZEagzMbXA003OROiS23JzzT7M0lLStvixwlg== +"@abp/timeago@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.1.0-rc.2.tgz#98d630cc3843eee64dbcc34fb8ca5afbab034718" + integrity sha512-vJmk+otyXXJE2s2J8iYpLVaFuNAYnIUSOitmi7umYnL+k/UE2KQhBXU7FR0/OBY9mAZYd+shaiGIU1LMSaJ+Xg== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" timeago "^1.6.7" -"@abp/utils@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.1.0-rc.1.tgz#e53ef81f39fd620b41ae2bfb69d06ee85ccb28ad" - integrity sha512-xBHIw5rmAttN5PL5C84L2znHkA2HMrP6HaA2aV5k6KU7BtOWJhviZnKJgbUBfufNQNeLwlThCHAfS7aOv/UQkg== +"@abp/utils@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.1.0-rc.2.tgz#86a980c6536b3b5ce185d406723b28be421864ac" + integrity sha512-Oz863VNA8fraQ81vTvqM0IqwiaseLwfFU5QNn6iOGOfn5wQrEkPwtZ0jMI+DGNtJgPzoKiq+iKc3K+SiuVgldg== dependencies: just-compare "^2.3.0" diff --git a/modules/blogging/app/Volo.BloggingTestApp/package.json b/modules/blogging/app/Volo.BloggingTestApp/package.json index 5a8ef2d655..0f4e62f9cf 100644 --- a/modules/blogging/app/Volo.BloggingTestApp/package.json +++ b/modules/blogging/app/Volo.BloggingTestApp/package.json @@ -3,7 +3,7 @@ "name": "volo.blogtestapp", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~10.1.0-rc.1", - "@abp/blogging": "~10.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.basic": "~10.1.0-rc.2", + "@abp/blogging": "~10.1.0-rc.2" } } diff --git a/modules/blogging/app/Volo.BloggingTestApp/yarn.lock b/modules/blogging/app/Volo.BloggingTestApp/yarn.lock index 7b82124f60..1f2cd9805d 100644 --- a/modules/blogging/app/Volo.BloggingTestApp/yarn.lock +++ b/modules/blogging/app/Volo.BloggingTestApp/yarn.lock @@ -2,228 +2,228 @@ # yarn lockfile v1 -"@abp/aspnetcore.mvc.ui.theme.basic@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-10.1.0-rc.1.tgz#99407c4108b685eef91f21d038cc5bd2343b5847" - integrity sha512-UbVgYf1K+zUqueNCwOl4QoUAz7nwJyoQJSqq0gi7LA9JBRBbb8nDA8szQjAUVqF4h+k4o61588MUQnerUQlZ6g== - dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~10.1.0-rc.1" - -"@abp/aspnetcore.mvc.ui.theme.shared@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.1.0-rc.1.tgz#7c0f721bdd4ec99f441a49e662a457d98c2c66fa" - integrity sha512-dB6wmR3C43Vn1/hiWY3IkxivUEWTGhO4mj3hffwAeyJqnEUh6kQUW6UfmYNGEhahSjhVIS3lUZ4x+R+yGCpemw== - dependencies: - "@abp/aspnetcore.mvc.ui" "~10.1.0-rc.1" - "@abp/bootstrap" "~10.1.0-rc.1" - "@abp/bootstrap-datepicker" "~10.1.0-rc.1" - "@abp/bootstrap-daterangepicker" "~10.1.0-rc.1" - "@abp/datatables.net-bs5" "~10.1.0-rc.1" - "@abp/font-awesome" "~10.1.0-rc.1" - "@abp/jquery-form" "~10.1.0-rc.1" - "@abp/jquery-validation-unobtrusive" "~10.1.0-rc.1" - "@abp/lodash" "~10.1.0-rc.1" - "@abp/luxon" "~10.1.0-rc.1" - "@abp/malihu-custom-scrollbar-plugin" "~10.1.0-rc.1" - "@abp/moment" "~10.1.0-rc.1" - "@abp/select2" "~10.1.0-rc.1" - "@abp/sweetalert2" "~10.1.0-rc.1" - "@abp/timeago" "~10.1.0-rc.1" - -"@abp/aspnetcore.mvc.ui@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.1.0-rc.1.tgz#290297353fdc15826a870b35162a0bbf4c9bc948" - integrity sha512-RBR5wPf5ygzuSpfXG5MeOGd/YOtQH5pVyU65T4DMtGpLKHQxgxbh7JK1btyC5zGmzr/Ds5+5Mcd7S66dIckDJQ== +"@abp/aspnetcore.mvc.ui.theme.basic@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-10.1.0-rc.2.tgz#599f5c47a417d1230fc17c0446a0229f920f7246" + integrity sha512-8F4nEK+VtgRRf8n+66HMbtCEaOMCW/OdbSEWRl9ahMNoj860oPIJ8P8Qn/2+LjtkPMdDAfCdEzyDzCd3igaFaA== + dependencies: + "@abp/aspnetcore.mvc.ui.theme.shared" "~10.1.0-rc.2" + +"@abp/aspnetcore.mvc.ui.theme.shared@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.1.0-rc.2.tgz#e5056e4e159f5815e3cffecab5c46f3d7d4f79d7" + integrity sha512-bo56XzQZPYL/3ckWTTTSSUsSFSFJobvfE29cz13NIrZ/tBtWyQCAJn92wYHuY+6IezYUWb4ga3PkFeHRzR142A== + dependencies: + "@abp/aspnetcore.mvc.ui" "~10.1.0-rc.2" + "@abp/bootstrap" "~10.1.0-rc.2" + "@abp/bootstrap-datepicker" "~10.1.0-rc.2" + "@abp/bootstrap-daterangepicker" "~10.1.0-rc.2" + "@abp/datatables.net-bs5" "~10.1.0-rc.2" + "@abp/font-awesome" "~10.1.0-rc.2" + "@abp/jquery-form" "~10.1.0-rc.2" + "@abp/jquery-validation-unobtrusive" "~10.1.0-rc.2" + "@abp/lodash" "~10.1.0-rc.2" + "@abp/luxon" "~10.1.0-rc.2" + "@abp/malihu-custom-scrollbar-plugin" "~10.1.0-rc.2" + "@abp/moment" "~10.1.0-rc.2" + "@abp/select2" "~10.1.0-rc.2" + "@abp/sweetalert2" "~10.1.0-rc.2" + "@abp/timeago" "~10.1.0-rc.2" + +"@abp/aspnetcore.mvc.ui@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.1.0-rc.2.tgz#e25d3575d40bfcb3f809bd2d355671181ee5ff40" + integrity sha512-MOF86bVbi7N/nIla+361nsBrN4tiSka8xzpWcgqlLcCAl9ILG4rugbtafBAjN81taPma2peZM7egaOR4SDkTMw== dependencies: ansi-colors "^4.1.3" -"@abp/blogging@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/blogging/-/blogging-10.1.0-rc.1.tgz#2438e08f4ceeb47b5448ca7e46de00ecd825112b" - integrity sha512-4D2JVdoXF0duIByQ6xeV3sU44ZLFQtkKgIjylDhlVuXUfCg8yKBqjK9MBNpV2Ltxj32HnKQjnPkj06P+zZL1ZA== +"@abp/blogging@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/blogging/-/blogging-10.1.0-rc.2.tgz#f60d5fdfef5be11cbbb23ad7b4b246621828e5f9" + integrity sha512-GcI6JWeQKcHA0FaJZYTgx9l63jlSn1cqaWjBx6Y4KYIpy1c8vDKnve85jzsj7UOKgkMFX1c7mN2vwzH3NSr1Qg== dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~10.1.0-rc.1" - "@abp/owl.carousel" "~10.1.0-rc.1" - "@abp/prismjs" "~10.1.0-rc.1" - "@abp/tui-editor" "~10.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.shared" "~10.1.0-rc.2" + "@abp/owl.carousel" "~10.1.0-rc.2" + "@abp/prismjs" "~10.1.0-rc.2" + "@abp/tui-editor" "~10.1.0-rc.2" -"@abp/bootstrap-datepicker@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.1.0-rc.1.tgz#54730dc001dab746af18c41d51b73949742e3b2d" - integrity sha512-DhVfA9WjfRQrUwjN2eJTCjLEhAU7rLjWgMlifW1id3HbdMs9emKn0rzQqhZ8MOSUNBwLkbUWcXWnojRUFgUB+w== +"@abp/bootstrap-datepicker@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.1.0-rc.2.tgz#be80c6104ba53e18935fbf62ca2c1890f4b2fde4" + integrity sha512-BNcDYUSbZaLah4SfXm0efoqFTsOViVm6370k9L7vix/OGpIWwklJsr8y78lvdM5ANgNCfl0LPSq+seLJFc/OLA== dependencies: bootstrap-datepicker "^1.10.1" -"@abp/bootstrap-daterangepicker@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.1.0-rc.1.tgz#9af7ffe1d1bd4fc99e1606e30189e6f28bb2f201" - integrity sha512-/Hsge8UwLZFuPNWRZhpf4nVB4urPYsaKAuCz0pSH5OHmS4FWr0Ng3vnUd9fcSeaq9OV2HarcgGWDapYtszttVQ== +"@abp/bootstrap-daterangepicker@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.1.0-rc.2.tgz#f189f7d070ebd97d9cfdcb99571cab2d6a198ab5" + integrity sha512-bV8J0MuiAFVLkr48JsB6aZU6aPoqw+Gyhq1szQ74bEwNQlRBPuF92WVA5FACaUBj8dMUzR9HDDAYQuxUzpKYKA== dependencies: bootstrap-daterangepicker "^3.1.0" -"@abp/bootstrap@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.1.0-rc.1.tgz#e937f0a10874c0f3b0f719ff6a316b0ca91e65c5" - integrity sha512-GlRyH5oCjcmFk1eBGCHk6GglL2DJ5RLlDXn7HWU675u48DCQTkBbKP9qA3RcYERQB4qqhcOKEDtYPZVljRVHRQ== +"@abp/bootstrap@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.1.0-rc.2.tgz#2300800a29ea09b91f5ed2e6177e5921fe7d2a0f" + integrity sha512-K+tDI9vz/Y9B/yu0i3AVpm4v3Odi44Q/yH5hAprL7f4pGxEOiqAFB/qzHAxG+7Oa7wjv5tPLv+Cz4DavBQjd8Q== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" bootstrap "^5.3.8" -"@abp/clipboard@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/clipboard/-/clipboard-10.1.0-rc.1.tgz#ebb677e689372d5c0d07a5814b61eae45397a3c8" - integrity sha512-iJpKYQJy5mN1O8n0+4DsWoWKAOasLuUE9Cja01Yl7YTb6XwYCGozBAVZbi+YouhYmsSr2zM4ozKxXPbXXt0u1w== +"@abp/clipboard@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/clipboard/-/clipboard-10.1.0-rc.2.tgz#e99dbf190e3684e99c8e909bf38201c70e267502" + integrity sha512-kRS9pWc1jRgr4D4/EV9zdAy3rhhGBrcqk2as5+6Ih49npsEJY/cF5mYH7mj/ZYy8SHqtae/CR7bZsR+uCDKYrQ== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" clipboard "^2.0.11" -"@abp/core@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.1.0-rc.1.tgz#d9ce0958407fa39c8015eb2d9328a3a157aaa4f0" - integrity sha512-pFsaQei1W0JRJkhNdlxq+EDP7x+83nT0UHZetTHNzp8tkP4Cn8Ni/ZODIUku8ck1vcKc0X3pXKj42BN+yZ1g3g== +"@abp/core@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.1.0-rc.2.tgz#403687aff5a30788f7b7ca660abdfd85d89438aa" + integrity sha512-euuG2Hna/DT6/R1dGOjgp3vcehYtF+CcOkRj31oquYKaM5YWk4OaZ314DSpnjgs/xo8DuVc4eKFQwIxD9RK41w== dependencies: - "@abp/utils" "~10.1.0-rc.1" + "@abp/utils" "~10.1.0-rc.2" -"@abp/datatables.net-bs5@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.1.0-rc.1.tgz#7981bc64c543d24aa5b0ab373f5693187990d5be" - integrity sha512-yaYHuZ9eVHT2cZcH9r680X+imWhOqbhWlOlbLDpJtg7Un0ejDzou+JqxKYHHXmSEKYEPHjfCEhnXkMnyqvSUHQ== +"@abp/datatables.net-bs5@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.1.0-rc.2.tgz#a60650d1802b40751d30f8f6c56beb23fd66481b" + integrity sha512-IWwexNqbMpET54Fvm9LoPTJYf+4CoBbjFOvz3sL6CgO2feV5R5fKigjVU8zXKNh2W+RG8L6zEarfVxrr114TsA== dependencies: - "@abp/datatables.net" "~10.1.0-rc.1" + "@abp/datatables.net" "~10.1.0-rc.2" datatables.net-bs5 "^2.3.4" -"@abp/datatables.net@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.1.0-rc.1.tgz#f487357690060dd055dbb7f54e1298e8d8a81e40" - integrity sha512-KTKTW73H9vaX6J9ipXUf7d3QdseKaFRzl7e1QKldOoQxBs4pzT2NATGdUyiAnJsfPl4scpSFetzI66Rs7r3idg== +"@abp/datatables.net@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.1.0-rc.2.tgz#9147f68bc6dbc4eb40a9ddf65c7859e788cdcac2" + integrity sha512-a9DJpwg14S4nVOiC4ipw0CQwEYWB602e2gCJiH7W1mxopbQb135RxwhtdTnW//eIONcxC9IrEuvcBEAUVt2B7Q== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" datatables.net "^2.3.4" -"@abp/font-awesome@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.1.0-rc.1.tgz#2ee67b07ce6296c7f0ac5217c9d9677b9c70f05e" - integrity sha512-O/3oLbSu1pMNftQzQW29gSpkWCFJazFKjT7sOYId1Ueric0svyJ42hFr5gHf9RFVmCU5pgyhawcoTyzQ34PxYQ== +"@abp/font-awesome@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.1.0-rc.2.tgz#364466cfe67e41e0c4d16b57d3923d10f66369f1" + integrity sha512-F1Jy8xoFV2aA+VN+NH1gtrG96/j9w7Picc+KLoCoIyNnJr/xJur11XkJyu5ln8KF4V7p/DY7QaQodWV/btOs8g== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" "@fortawesome/fontawesome-free" "^7.0.1" -"@abp/jquery-form@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-10.1.0-rc.1.tgz#60fc98085671a2ff08e8c411847354b6a48cf55e" - integrity sha512-bj70hE7MsH2AoBb2yU2d6ng5Bo/wqjCNUI0kbG0T1T4yp1cToGAVJ6hrd+KOp2Tf6W+NITn1EycxRTCktE9QRA== +"@abp/jquery-form@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-10.1.0-rc.2.tgz#3857717d07569c22d4bbbe459238abeb816d606a" + integrity sha512-2D5WHVnfK9bhRces1tgPwOEoc7KCYKYiKHBOcqct+LTA7zoRjJv/PM8/JhFVl+grVIw1aSwO4tU3YfZ22Vxipg== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" jquery-form "^4.3.0" -"@abp/jquery-validation-unobtrusive@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.1.0-rc.1.tgz#a820989d8daf53096f838817cb2b3dba21f71653" - integrity sha512-7uXxbvuZ+ecD5suH3nx/ggiFAA8GjXGAB5tuGCdqePdBn1PMzDIDI1ZP9q8sR7sMjo7RKVK6MzX+PazUuJ4L7g== +"@abp/jquery-validation-unobtrusive@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.1.0-rc.2.tgz#efd7b69a078a20c0bf405408dbdf52a7bf770b3b" + integrity sha512-tZ0MWgzBqp+SNfMxM0z2cGB21NiTHuVJyyQaXKE/ptuD5pc0uRkcqw/J2kWfiqsoVgChz27IB6h8/jqDafS4qg== dependencies: - "@abp/jquery-validation" "~10.1.0-rc.1" + "@abp/jquery-validation" "~10.1.0-rc.2" jquery-validation-unobtrusive "^4.0.0" -"@abp/jquery-validation@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.1.0-rc.1.tgz#c8630b0a53ee93218be5c82264085b36b0b07936" - integrity sha512-/eS4vy5gwHIaRr6aHkvO6xWRjL22bVly7S0AqbZw2OxJ0pw1n23n+zipn/NDRSngRZSajTLLm1pYCG5qlJGd3A== +"@abp/jquery-validation@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.1.0-rc.2.tgz#d39537a7356c51f9db2e66f6740cf6df86bd0442" + integrity sha512-LOkS0NKk4pLtLjPU0CCbwROyUg6EtJN8Z/it7QuKK1CIRfYYcAStgNnNm5geZP7CqECIkoiFfgWjI+L5Z9/Tfg== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" jquery-validation "^1.21.0" -"@abp/jquery@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.1.0-rc.1.tgz#38d5d5b5a6144bf207773ef26f36d381a257a635" - integrity sha512-YarNlYPG7MfLc0fSnOcVrp9Da1EUxz4zUERnTOQ0/EdOUdcFhGAqI2yfoTFCxqX+TSphQWZEdif/tsfULtkI1A== +"@abp/jquery@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.1.0-rc.2.tgz#101a55f70d510978c8c05f5857d0e9d4965263f7" + integrity sha512-bQV1uFWGtwRYjNOsqJ8FM2004idX2Jj7YVL19YF1/PjyPUSMX+s8/IvJizBjyY5hPAiWBBhmV9g+IFWzxlDQoQ== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" jquery "~3.7.1" -"@abp/lodash@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.1.0-rc.1.tgz#bf735e34a42ddcdf19e331c8c5cf1a24a87475cb" - integrity sha512-JQV0oFLE90V9GdrD37PTMiOylw/U6yjpDu5PblVW/zlNmw/TzEtukp3/vJl7GJxoKRYWtgJf1nFLgG/+2kjE1A== +"@abp/lodash@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.1.0-rc.2.tgz#d08c03f8d3d0fbaa3e71e603cbe5fb7f176933ef" + integrity sha512-KCnD1p2y52ZI+2ifpiFIUAiDPsKehnOD8HV5qKeObO6UCP97okif8IP+sQDmNQb8O33y/NKTyx/HcpwBbe/NYQ== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" lodash "^4.17.21" -"@abp/luxon@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.1.0-rc.1.tgz#1c883ad3a5653f0c6ce4965e4b4ce21543129534" - integrity sha512-SDtwjIDR4OzrtzRWpgb8atUSK0DwajJ5LC486xHq5ftnbFETRfOwihyIfC2H4oIPPDE5oS+LOnjCkRYVkGt0oQ== +"@abp/luxon@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.1.0-rc.2.tgz#ef8d2b323bac054fc9610e241e1b1763d229e065" + integrity sha512-qYFl6XO3g9mZiu0dtIczI7LRuYWwc+RkpbDzSmruXcRks3KA+ZZco2vhHNnlwtXcINl/TXtbW7Wc0MX+8IB1Kw== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" luxon "^3.7.2" -"@abp/malihu-custom-scrollbar-plugin@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.1.0-rc.1.tgz#770106795f5eba9d36dd2d4002e450c215c66787" - integrity sha512-EFwQ7rUiPM6mxznn1t23ySzQH+VQ0dB2YxOqBAluFR/yC3qypjb98jGQaZKsJx9+iT4DuG9gGPKOfct1Sqh0KQ== +"@abp/malihu-custom-scrollbar-plugin@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.1.0-rc.2.tgz#dfaf666442c7c122f7da72c83b9adf194d5b6ec8" + integrity sha512-PudMHmNQgZ6JZeaVt1ZoXLqO0UZXJzUYiBah2LDkC4EMLjnMJFINHBoEVVa4ooXH0yjFv+zsbN0vWZYJ8TBJIA== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/moment@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.1.0-rc.1.tgz#fe5bfa5bc28bb847cf801523c1b2f118ffb12447" - integrity sha512-P6z9YO/FC/5DhTDWsHkquo5tEzIhSfUnRefLcZtv7XuJtevH/93NFSr23RL6D+F4r3cB5GMXGFUYmXGKWoXGHg== +"@abp/moment@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.1.0-rc.2.tgz#610a1592d13984aea51abbd13df8c5995a089149" + integrity sha512-ep8PnAXARw0t/wtGOVp/oiNhF3B0Bh6y2vRzKrcSoyXAQREGGm4fJdZVYZLGTfI4lFLTjebEgf4O7T9feUwJAw== dependencies: moment "^2.30.1" -"@abp/owl.carousel@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/owl.carousel/-/owl.carousel-10.1.0-rc.1.tgz#180edebe4fbeaf8fb9c468f6104c0ca14c9040a7" - integrity sha512-udlMHMqoqhUBP0Xy1pJ9XBWHKjgNDxdXeW/QqgJwb1tFE1iyHpL73gaJLSfA1RzwDnWiweWDTcm7zzXhz2UpHQ== +"@abp/owl.carousel@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/owl.carousel/-/owl.carousel-10.1.0-rc.2.tgz#e7697b7c8954472758547688fa6724b219a5c99c" + integrity sha512-XKciT8HNWoZvlcMGRwOz9opld4BJsAQwMKsKRu1oD4/KMCnT7LelacFPVbj3CLhsLpU+2eq0M947fEI7hQiPOQ== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" owl.carousel "^2.3.4" -"@abp/prismjs@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/prismjs/-/prismjs-10.1.0-rc.1.tgz#26ef6c368dbfdd27d2836f1dd8cd9e8cf630f0ab" - integrity sha512-6OpD/w7NOSI7OpjubMx45gXsZY5K6ggynJ9uSqv3NDxSbj9l6c8cRTX1i56ATT10rhyYxZIw83FRhMlxOiLfjg== +"@abp/prismjs@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/prismjs/-/prismjs-10.1.0-rc.2.tgz#8565bab503a16fc349f4b0fa2609ad412ff838be" + integrity sha512-SmZWMyJ3cJW+qj4CWJ7y2kD6PMx2zfZMA5X5jPunsytG4Eht4AVyIR38Y4QSpO62zZgkHyZlSTFOozBfhrlv9A== dependencies: - "@abp/clipboard" "~10.1.0-rc.1" - "@abp/core" "~10.1.0-rc.1" + "@abp/clipboard" "~10.1.0-rc.2" + "@abp/core" "~10.1.0-rc.2" prismjs "^1.30.0" -"@abp/select2@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.1.0-rc.1.tgz#19432bb6483f8a3470f02a3037612835b4c54ef4" - integrity sha512-xPvdrgXZzRhEIrEyw9eoITc+hYlbi8fSW3BGQ1ZMBXWgYkjWxhj/1AuLsu2ewFAKdBNz+PewjA0MKP6iBLATHw== +"@abp/select2@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.1.0-rc.2.tgz#40c5418d007fc36817eecbe6388d767e4e7ca887" + integrity sha512-Pq0wlpL01sWRLUg5um3JtBXIqi3mmbwPwvgxP8hFbQngAt9JXAK8geNRiTMrIZgtW/ycXtM1v6I4zuWOLOeAGg== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" select2 "^4.0.13" -"@abp/sweetalert2@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.1.0-rc.1.tgz#40f4d6964cd6620dcb81040d84e432f4ae314686" - integrity sha512-E4dUC2k/kClBnadtGK5NWR9YmCGgKq5uOGjEVtzXxJH62MlaCXlcxRrK9wNzX8pzDMDYFKjuOUugoROEgbRKWQ== +"@abp/sweetalert2@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.1.0-rc.2.tgz#d35858c69e10c6726b02cdfcea88dfc32385963f" + integrity sha512-s9VPRToohN45uzHcKCF5Mcj8FVjsXcXUb0U3tuaT/Y+u4adHB3fBxYiXJFM0sVsCJ81dFktxwka40Wm8Taz/zA== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" sweetalert2 "^11.23.0" -"@abp/timeago@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.1.0-rc.1.tgz#482e3a645f63ee9adf708ef8ae895fcf2bcb4271" - integrity sha512-UOb2o6bvi3+2kbD7+rUJdqTQvTbgqUD7ktd0U5Hulb3dBOyv6MZEagzMbXA003OROiS23JzzT7M0lLStvixwlg== +"@abp/timeago@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.1.0-rc.2.tgz#98d630cc3843eee64dbcc34fb8ca5afbab034718" + integrity sha512-vJmk+otyXXJE2s2J8iYpLVaFuNAYnIUSOitmi7umYnL+k/UE2KQhBXU7FR0/OBY9mAZYd+shaiGIU1LMSaJ+Xg== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" timeago "^1.6.7" -"@abp/tui-editor@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/tui-editor/-/tui-editor-10.1.0-rc.1.tgz#fd9d014ea5bbdc9c93b3160040928cc27ba50d5e" - integrity sha512-X6tlZs3/soUbl/L/aiQfIOZuiZRfmlUpEukIduJBlDsSaNR2hJgEa98RsQNvQ2yvSU/H7+9kwIwdizhQdimujA== +"@abp/tui-editor@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/tui-editor/-/tui-editor-10.1.0-rc.2.tgz#ebbd5bad1ee180a0c6e6a9cfd894499614a71e96" + integrity sha512-k5V+5ZE+HZebfyXLzddRQDGri3HP7wSjDXEbSMLTgxZTem7IzksyLWLAN/woKRzWX92BJXcsmR8T1rhuMhohhA== dependencies: - "@abp/jquery" "~10.1.0-rc.1" - "@abp/prismjs" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" + "@abp/prismjs" "~10.1.0-rc.2" -"@abp/utils@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.1.0-rc.1.tgz#e53ef81f39fd620b41ae2bfb69d06ee85ccb28ad" - integrity sha512-xBHIw5rmAttN5PL5C84L2znHkA2HMrP6HaA2aV5k6KU7BtOWJhviZnKJgbUBfufNQNeLwlThCHAfS7aOv/UQkg== +"@abp/utils@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.1.0-rc.2.tgz#86a980c6536b3b5ce185d406723b28be421864ac" + integrity sha512-Oz863VNA8fraQ81vTvqM0IqwiaseLwfFU5QNn6iOGOfn5wQrEkPwtZ0jMI+DGNtJgPzoKiq+iKc3K+SiuVgldg== dependencies: just-compare "^2.3.0" diff --git a/modules/client-simulation/demo/Volo.ClientSimulation.Demo/package.json b/modules/client-simulation/demo/Volo.ClientSimulation.Demo/package.json index 10810ab195..66592df9ff 100644 --- a/modules/client-simulation/demo/Volo.ClientSimulation.Demo/package.json +++ b/modules/client-simulation/demo/Volo.ClientSimulation.Demo/package.json @@ -3,6 +3,6 @@ "name": "client-simulation-web", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~10.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.basic": "~10.1.0-rc.2" } } diff --git a/modules/client-simulation/demo/Volo.ClientSimulation.Demo/yarn.lock b/modules/client-simulation/demo/Volo.ClientSimulation.Demo/yarn.lock index 55a960f9ab..a2b6e13b29 100644 --- a/modules/client-simulation/demo/Volo.ClientSimulation.Demo/yarn.lock +++ b/modules/client-simulation/demo/Volo.ClientSimulation.Demo/yarn.lock @@ -2,185 +2,185 @@ # yarn lockfile v1 -"@abp/aspnetcore.mvc.ui.theme.basic@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-10.1.0-rc.1.tgz#99407c4108b685eef91f21d038cc5bd2343b5847" - integrity sha512-UbVgYf1K+zUqueNCwOl4QoUAz7nwJyoQJSqq0gi7LA9JBRBbb8nDA8szQjAUVqF4h+k4o61588MUQnerUQlZ6g== +"@abp/aspnetcore.mvc.ui.theme.basic@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-10.1.0-rc.2.tgz#599f5c47a417d1230fc17c0446a0229f920f7246" + integrity sha512-8F4nEK+VtgRRf8n+66HMbtCEaOMCW/OdbSEWRl9ahMNoj860oPIJ8P8Qn/2+LjtkPMdDAfCdEzyDzCd3igaFaA== dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~10.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.shared" "~10.1.0-rc.2" -"@abp/aspnetcore.mvc.ui.theme.shared@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.1.0-rc.1.tgz#7c0f721bdd4ec99f441a49e662a457d98c2c66fa" - integrity sha512-dB6wmR3C43Vn1/hiWY3IkxivUEWTGhO4mj3hffwAeyJqnEUh6kQUW6UfmYNGEhahSjhVIS3lUZ4x+R+yGCpemw== +"@abp/aspnetcore.mvc.ui.theme.shared@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.1.0-rc.2.tgz#e5056e4e159f5815e3cffecab5c46f3d7d4f79d7" + integrity sha512-bo56XzQZPYL/3ckWTTTSSUsSFSFJobvfE29cz13NIrZ/tBtWyQCAJn92wYHuY+6IezYUWb4ga3PkFeHRzR142A== dependencies: - "@abp/aspnetcore.mvc.ui" "~10.1.0-rc.1" - "@abp/bootstrap" "~10.1.0-rc.1" - "@abp/bootstrap-datepicker" "~10.1.0-rc.1" - "@abp/bootstrap-daterangepicker" "~10.1.0-rc.1" - "@abp/datatables.net-bs5" "~10.1.0-rc.1" - "@abp/font-awesome" "~10.1.0-rc.1" - "@abp/jquery-form" "~10.1.0-rc.1" - "@abp/jquery-validation-unobtrusive" "~10.1.0-rc.1" - "@abp/lodash" "~10.1.0-rc.1" - "@abp/luxon" "~10.1.0-rc.1" - "@abp/malihu-custom-scrollbar-plugin" "~10.1.0-rc.1" - "@abp/moment" "~10.1.0-rc.1" - "@abp/select2" "~10.1.0-rc.1" - "@abp/sweetalert2" "~10.1.0-rc.1" - "@abp/timeago" "~10.1.0-rc.1" - -"@abp/aspnetcore.mvc.ui@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.1.0-rc.1.tgz#290297353fdc15826a870b35162a0bbf4c9bc948" - integrity sha512-RBR5wPf5ygzuSpfXG5MeOGd/YOtQH5pVyU65T4DMtGpLKHQxgxbh7JK1btyC5zGmzr/Ds5+5Mcd7S66dIckDJQ== + "@abp/aspnetcore.mvc.ui" "~10.1.0-rc.2" + "@abp/bootstrap" "~10.1.0-rc.2" + "@abp/bootstrap-datepicker" "~10.1.0-rc.2" + "@abp/bootstrap-daterangepicker" "~10.1.0-rc.2" + "@abp/datatables.net-bs5" "~10.1.0-rc.2" + "@abp/font-awesome" "~10.1.0-rc.2" + "@abp/jquery-form" "~10.1.0-rc.2" + "@abp/jquery-validation-unobtrusive" "~10.1.0-rc.2" + "@abp/lodash" "~10.1.0-rc.2" + "@abp/luxon" "~10.1.0-rc.2" + "@abp/malihu-custom-scrollbar-plugin" "~10.1.0-rc.2" + "@abp/moment" "~10.1.0-rc.2" + "@abp/select2" "~10.1.0-rc.2" + "@abp/sweetalert2" "~10.1.0-rc.2" + "@abp/timeago" "~10.1.0-rc.2" + +"@abp/aspnetcore.mvc.ui@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.1.0-rc.2.tgz#e25d3575d40bfcb3f809bd2d355671181ee5ff40" + integrity sha512-MOF86bVbi7N/nIla+361nsBrN4tiSka8xzpWcgqlLcCAl9ILG4rugbtafBAjN81taPma2peZM7egaOR4SDkTMw== dependencies: ansi-colors "^4.1.3" -"@abp/bootstrap-datepicker@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.1.0-rc.1.tgz#54730dc001dab746af18c41d51b73949742e3b2d" - integrity sha512-DhVfA9WjfRQrUwjN2eJTCjLEhAU7rLjWgMlifW1id3HbdMs9emKn0rzQqhZ8MOSUNBwLkbUWcXWnojRUFgUB+w== +"@abp/bootstrap-datepicker@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.1.0-rc.2.tgz#be80c6104ba53e18935fbf62ca2c1890f4b2fde4" + integrity sha512-BNcDYUSbZaLah4SfXm0efoqFTsOViVm6370k9L7vix/OGpIWwklJsr8y78lvdM5ANgNCfl0LPSq+seLJFc/OLA== dependencies: bootstrap-datepicker "^1.10.1" -"@abp/bootstrap-daterangepicker@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.1.0-rc.1.tgz#9af7ffe1d1bd4fc99e1606e30189e6f28bb2f201" - integrity sha512-/Hsge8UwLZFuPNWRZhpf4nVB4urPYsaKAuCz0pSH5OHmS4FWr0Ng3vnUd9fcSeaq9OV2HarcgGWDapYtszttVQ== +"@abp/bootstrap-daterangepicker@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.1.0-rc.2.tgz#f189f7d070ebd97d9cfdcb99571cab2d6a198ab5" + integrity sha512-bV8J0MuiAFVLkr48JsB6aZU6aPoqw+Gyhq1szQ74bEwNQlRBPuF92WVA5FACaUBj8dMUzR9HDDAYQuxUzpKYKA== dependencies: bootstrap-daterangepicker "^3.1.0" -"@abp/bootstrap@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.1.0-rc.1.tgz#e937f0a10874c0f3b0f719ff6a316b0ca91e65c5" - integrity sha512-GlRyH5oCjcmFk1eBGCHk6GglL2DJ5RLlDXn7HWU675u48DCQTkBbKP9qA3RcYERQB4qqhcOKEDtYPZVljRVHRQ== +"@abp/bootstrap@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.1.0-rc.2.tgz#2300800a29ea09b91f5ed2e6177e5921fe7d2a0f" + integrity sha512-K+tDI9vz/Y9B/yu0i3AVpm4v3Odi44Q/yH5hAprL7f4pGxEOiqAFB/qzHAxG+7Oa7wjv5tPLv+Cz4DavBQjd8Q== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" bootstrap "^5.3.8" -"@abp/core@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.1.0-rc.1.tgz#d9ce0958407fa39c8015eb2d9328a3a157aaa4f0" - integrity sha512-pFsaQei1W0JRJkhNdlxq+EDP7x+83nT0UHZetTHNzp8tkP4Cn8Ni/ZODIUku8ck1vcKc0X3pXKj42BN+yZ1g3g== +"@abp/core@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.1.0-rc.2.tgz#403687aff5a30788f7b7ca660abdfd85d89438aa" + integrity sha512-euuG2Hna/DT6/R1dGOjgp3vcehYtF+CcOkRj31oquYKaM5YWk4OaZ314DSpnjgs/xo8DuVc4eKFQwIxD9RK41w== dependencies: - "@abp/utils" "~10.1.0-rc.1" + "@abp/utils" "~10.1.0-rc.2" -"@abp/datatables.net-bs5@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.1.0-rc.1.tgz#7981bc64c543d24aa5b0ab373f5693187990d5be" - integrity sha512-yaYHuZ9eVHT2cZcH9r680X+imWhOqbhWlOlbLDpJtg7Un0ejDzou+JqxKYHHXmSEKYEPHjfCEhnXkMnyqvSUHQ== +"@abp/datatables.net-bs5@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.1.0-rc.2.tgz#a60650d1802b40751d30f8f6c56beb23fd66481b" + integrity sha512-IWwexNqbMpET54Fvm9LoPTJYf+4CoBbjFOvz3sL6CgO2feV5R5fKigjVU8zXKNh2W+RG8L6zEarfVxrr114TsA== dependencies: - "@abp/datatables.net" "~10.1.0-rc.1" + "@abp/datatables.net" "~10.1.0-rc.2" datatables.net-bs5 "^2.3.4" -"@abp/datatables.net@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.1.0-rc.1.tgz#f487357690060dd055dbb7f54e1298e8d8a81e40" - integrity sha512-KTKTW73H9vaX6J9ipXUf7d3QdseKaFRzl7e1QKldOoQxBs4pzT2NATGdUyiAnJsfPl4scpSFetzI66Rs7r3idg== +"@abp/datatables.net@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.1.0-rc.2.tgz#9147f68bc6dbc4eb40a9ddf65c7859e788cdcac2" + integrity sha512-a9DJpwg14S4nVOiC4ipw0CQwEYWB602e2gCJiH7W1mxopbQb135RxwhtdTnW//eIONcxC9IrEuvcBEAUVt2B7Q== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" datatables.net "^2.3.4" -"@abp/font-awesome@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.1.0-rc.1.tgz#2ee67b07ce6296c7f0ac5217c9d9677b9c70f05e" - integrity sha512-O/3oLbSu1pMNftQzQW29gSpkWCFJazFKjT7sOYId1Ueric0svyJ42hFr5gHf9RFVmCU5pgyhawcoTyzQ34PxYQ== +"@abp/font-awesome@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.1.0-rc.2.tgz#364466cfe67e41e0c4d16b57d3923d10f66369f1" + integrity sha512-F1Jy8xoFV2aA+VN+NH1gtrG96/j9w7Picc+KLoCoIyNnJr/xJur11XkJyu5ln8KF4V7p/DY7QaQodWV/btOs8g== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" "@fortawesome/fontawesome-free" "^7.0.1" -"@abp/jquery-form@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-10.1.0-rc.1.tgz#60fc98085671a2ff08e8c411847354b6a48cf55e" - integrity sha512-bj70hE7MsH2AoBb2yU2d6ng5Bo/wqjCNUI0kbG0T1T4yp1cToGAVJ6hrd+KOp2Tf6W+NITn1EycxRTCktE9QRA== +"@abp/jquery-form@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-10.1.0-rc.2.tgz#3857717d07569c22d4bbbe459238abeb816d606a" + integrity sha512-2D5WHVnfK9bhRces1tgPwOEoc7KCYKYiKHBOcqct+LTA7zoRjJv/PM8/JhFVl+grVIw1aSwO4tU3YfZ22Vxipg== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" jquery-form "^4.3.0" -"@abp/jquery-validation-unobtrusive@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.1.0-rc.1.tgz#a820989d8daf53096f838817cb2b3dba21f71653" - integrity sha512-7uXxbvuZ+ecD5suH3nx/ggiFAA8GjXGAB5tuGCdqePdBn1PMzDIDI1ZP9q8sR7sMjo7RKVK6MzX+PazUuJ4L7g== +"@abp/jquery-validation-unobtrusive@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.1.0-rc.2.tgz#efd7b69a078a20c0bf405408dbdf52a7bf770b3b" + integrity sha512-tZ0MWgzBqp+SNfMxM0z2cGB21NiTHuVJyyQaXKE/ptuD5pc0uRkcqw/J2kWfiqsoVgChz27IB6h8/jqDafS4qg== dependencies: - "@abp/jquery-validation" "~10.1.0-rc.1" + "@abp/jquery-validation" "~10.1.0-rc.2" jquery-validation-unobtrusive "^4.0.0" -"@abp/jquery-validation@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.1.0-rc.1.tgz#c8630b0a53ee93218be5c82264085b36b0b07936" - integrity sha512-/eS4vy5gwHIaRr6aHkvO6xWRjL22bVly7S0AqbZw2OxJ0pw1n23n+zipn/NDRSngRZSajTLLm1pYCG5qlJGd3A== +"@abp/jquery-validation@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.1.0-rc.2.tgz#d39537a7356c51f9db2e66f6740cf6df86bd0442" + integrity sha512-LOkS0NKk4pLtLjPU0CCbwROyUg6EtJN8Z/it7QuKK1CIRfYYcAStgNnNm5geZP7CqECIkoiFfgWjI+L5Z9/Tfg== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" jquery-validation "^1.21.0" -"@abp/jquery@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.1.0-rc.1.tgz#38d5d5b5a6144bf207773ef26f36d381a257a635" - integrity sha512-YarNlYPG7MfLc0fSnOcVrp9Da1EUxz4zUERnTOQ0/EdOUdcFhGAqI2yfoTFCxqX+TSphQWZEdif/tsfULtkI1A== +"@abp/jquery@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.1.0-rc.2.tgz#101a55f70d510978c8c05f5857d0e9d4965263f7" + integrity sha512-bQV1uFWGtwRYjNOsqJ8FM2004idX2Jj7YVL19YF1/PjyPUSMX+s8/IvJizBjyY5hPAiWBBhmV9g+IFWzxlDQoQ== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" jquery "~3.7.1" -"@abp/lodash@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.1.0-rc.1.tgz#bf735e34a42ddcdf19e331c8c5cf1a24a87475cb" - integrity sha512-JQV0oFLE90V9GdrD37PTMiOylw/U6yjpDu5PblVW/zlNmw/TzEtukp3/vJl7GJxoKRYWtgJf1nFLgG/+2kjE1A== +"@abp/lodash@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.1.0-rc.2.tgz#d08c03f8d3d0fbaa3e71e603cbe5fb7f176933ef" + integrity sha512-KCnD1p2y52ZI+2ifpiFIUAiDPsKehnOD8HV5qKeObO6UCP97okif8IP+sQDmNQb8O33y/NKTyx/HcpwBbe/NYQ== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" lodash "^4.17.21" -"@abp/luxon@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.1.0-rc.1.tgz#1c883ad3a5653f0c6ce4965e4b4ce21543129534" - integrity sha512-SDtwjIDR4OzrtzRWpgb8atUSK0DwajJ5LC486xHq5ftnbFETRfOwihyIfC2H4oIPPDE5oS+LOnjCkRYVkGt0oQ== +"@abp/luxon@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.1.0-rc.2.tgz#ef8d2b323bac054fc9610e241e1b1763d229e065" + integrity sha512-qYFl6XO3g9mZiu0dtIczI7LRuYWwc+RkpbDzSmruXcRks3KA+ZZco2vhHNnlwtXcINl/TXtbW7Wc0MX+8IB1Kw== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" luxon "^3.7.2" -"@abp/malihu-custom-scrollbar-plugin@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.1.0-rc.1.tgz#770106795f5eba9d36dd2d4002e450c215c66787" - integrity sha512-EFwQ7rUiPM6mxznn1t23ySzQH+VQ0dB2YxOqBAluFR/yC3qypjb98jGQaZKsJx9+iT4DuG9gGPKOfct1Sqh0KQ== +"@abp/malihu-custom-scrollbar-plugin@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.1.0-rc.2.tgz#dfaf666442c7c122f7da72c83b9adf194d5b6ec8" + integrity sha512-PudMHmNQgZ6JZeaVt1ZoXLqO0UZXJzUYiBah2LDkC4EMLjnMJFINHBoEVVa4ooXH0yjFv+zsbN0vWZYJ8TBJIA== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/moment@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.1.0-rc.1.tgz#fe5bfa5bc28bb847cf801523c1b2f118ffb12447" - integrity sha512-P6z9YO/FC/5DhTDWsHkquo5tEzIhSfUnRefLcZtv7XuJtevH/93NFSr23RL6D+F4r3cB5GMXGFUYmXGKWoXGHg== +"@abp/moment@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.1.0-rc.2.tgz#610a1592d13984aea51abbd13df8c5995a089149" + integrity sha512-ep8PnAXARw0t/wtGOVp/oiNhF3B0Bh6y2vRzKrcSoyXAQREGGm4fJdZVYZLGTfI4lFLTjebEgf4O7T9feUwJAw== dependencies: moment "^2.30.1" -"@abp/select2@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.1.0-rc.1.tgz#19432bb6483f8a3470f02a3037612835b4c54ef4" - integrity sha512-xPvdrgXZzRhEIrEyw9eoITc+hYlbi8fSW3BGQ1ZMBXWgYkjWxhj/1AuLsu2ewFAKdBNz+PewjA0MKP6iBLATHw== +"@abp/select2@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.1.0-rc.2.tgz#40c5418d007fc36817eecbe6388d767e4e7ca887" + integrity sha512-Pq0wlpL01sWRLUg5um3JtBXIqi3mmbwPwvgxP8hFbQngAt9JXAK8geNRiTMrIZgtW/ycXtM1v6I4zuWOLOeAGg== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" select2 "^4.0.13" -"@abp/sweetalert2@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.1.0-rc.1.tgz#40f4d6964cd6620dcb81040d84e432f4ae314686" - integrity sha512-E4dUC2k/kClBnadtGK5NWR9YmCGgKq5uOGjEVtzXxJH62MlaCXlcxRrK9wNzX8pzDMDYFKjuOUugoROEgbRKWQ== +"@abp/sweetalert2@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.1.0-rc.2.tgz#d35858c69e10c6726b02cdfcea88dfc32385963f" + integrity sha512-s9VPRToohN45uzHcKCF5Mcj8FVjsXcXUb0U3tuaT/Y+u4adHB3fBxYiXJFM0sVsCJ81dFktxwka40Wm8Taz/zA== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" sweetalert2 "^11.23.0" -"@abp/timeago@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.1.0-rc.1.tgz#482e3a645f63ee9adf708ef8ae895fcf2bcb4271" - integrity sha512-UOb2o6bvi3+2kbD7+rUJdqTQvTbgqUD7ktd0U5Hulb3dBOyv6MZEagzMbXA003OROiS23JzzT7M0lLStvixwlg== +"@abp/timeago@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.1.0-rc.2.tgz#98d630cc3843eee64dbcc34fb8ca5afbab034718" + integrity sha512-vJmk+otyXXJE2s2J8iYpLVaFuNAYnIUSOitmi7umYnL+k/UE2KQhBXU7FR0/OBY9mAZYd+shaiGIU1LMSaJ+Xg== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" timeago "^1.6.7" -"@abp/utils@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.1.0-rc.1.tgz#e53ef81f39fd620b41ae2bfb69d06ee85ccb28ad" - integrity sha512-xBHIw5rmAttN5PL5C84L2znHkA2HMrP6HaA2aV5k6KU7BtOWJhviZnKJgbUBfufNQNeLwlThCHAfS7aOv/UQkg== +"@abp/utils@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.1.0-rc.2.tgz#86a980c6536b3b5ce185d406723b28be421864ac" + integrity sha512-Oz863VNA8fraQ81vTvqM0IqwiaseLwfFU5QNn6iOGOfn5wQrEkPwtZ0jMI+DGNtJgPzoKiq+iKc3K+SiuVgldg== dependencies: just-compare "^2.3.0" diff --git a/modules/cms-kit/angular/package.json b/modules/cms-kit/angular/package.json index 9badfe81b0..d4bc7712ef 100644 --- a/modules/cms-kit/angular/package.json +++ b/modules/cms-kit/angular/package.json @@ -15,11 +15,11 @@ }, "private": true, "dependencies": { - "@abp/ng.account": "~10.1.0-rc.1", - "@abp/ng.identity": "~10.1.0-rc.1", + "@abp/ng.account": "~10.1.0-rc.2", + "@abp/ng.identity": "~10.1.0-rc.2", "@abp/ng.setting-management": "~10.1.0-rc.1", - "@abp/ng.tenant-management": "~10.1.0-rc.1", - "@abp/ng.theme.basic": "~10.1.0-rc.1", + "@abp/ng.tenant-management": "~10.1.0-rc.2", + "@abp/ng.theme.basic": "~10.1.0-rc.2", "@angular/animations": "~10.0.0", "@angular/common": "~10.0.0", "@angular/compiler": "~10.0.0", diff --git a/modules/cms-kit/angular/projects/cms-kit/package.json b/modules/cms-kit/angular/projects/cms-kit/package.json index 6aa3f4aee0..aa92688cca 100644 --- a/modules/cms-kit/angular/projects/cms-kit/package.json +++ b/modules/cms-kit/angular/projects/cms-kit/package.json @@ -4,8 +4,8 @@ "peerDependencies": { "@angular/common": "^9.1.11", "@angular/core": "^9.1.11", - "@abp/ng.core": ">=10.1.0-rc.1", - "@abp/ng.theme.shared": ">=10.1.0-rc.1" + "@abp/ng.core": ">=10.1.0-rc.2", + "@abp/ng.theme.shared": ">=10.1.0-rc.2" }, "dependencies": { "tslib": "^2.0.0" diff --git a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/package.json b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/package.json index 5160154a29..7588fb2989 100644 --- a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/package.json +++ b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/package.json @@ -3,6 +3,6 @@ "name": "my-app-identityserver", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~10.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.basic": "~10.1.0-rc.2" } } diff --git a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/yarn.lock b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/yarn.lock index 55a960f9ab..a2b6e13b29 100644 --- a/modules/cms-kit/host/Volo.CmsKit.IdentityServer/yarn.lock +++ b/modules/cms-kit/host/Volo.CmsKit.IdentityServer/yarn.lock @@ -2,185 +2,185 @@ # yarn lockfile v1 -"@abp/aspnetcore.mvc.ui.theme.basic@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-10.1.0-rc.1.tgz#99407c4108b685eef91f21d038cc5bd2343b5847" - integrity sha512-UbVgYf1K+zUqueNCwOl4QoUAz7nwJyoQJSqq0gi7LA9JBRBbb8nDA8szQjAUVqF4h+k4o61588MUQnerUQlZ6g== +"@abp/aspnetcore.mvc.ui.theme.basic@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-10.1.0-rc.2.tgz#599f5c47a417d1230fc17c0446a0229f920f7246" + integrity sha512-8F4nEK+VtgRRf8n+66HMbtCEaOMCW/OdbSEWRl9ahMNoj860oPIJ8P8Qn/2+LjtkPMdDAfCdEzyDzCd3igaFaA== dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~10.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.shared" "~10.1.0-rc.2" -"@abp/aspnetcore.mvc.ui.theme.shared@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.1.0-rc.1.tgz#7c0f721bdd4ec99f441a49e662a457d98c2c66fa" - integrity sha512-dB6wmR3C43Vn1/hiWY3IkxivUEWTGhO4mj3hffwAeyJqnEUh6kQUW6UfmYNGEhahSjhVIS3lUZ4x+R+yGCpemw== +"@abp/aspnetcore.mvc.ui.theme.shared@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.1.0-rc.2.tgz#e5056e4e159f5815e3cffecab5c46f3d7d4f79d7" + integrity sha512-bo56XzQZPYL/3ckWTTTSSUsSFSFJobvfE29cz13NIrZ/tBtWyQCAJn92wYHuY+6IezYUWb4ga3PkFeHRzR142A== dependencies: - "@abp/aspnetcore.mvc.ui" "~10.1.0-rc.1" - "@abp/bootstrap" "~10.1.0-rc.1" - "@abp/bootstrap-datepicker" "~10.1.0-rc.1" - "@abp/bootstrap-daterangepicker" "~10.1.0-rc.1" - "@abp/datatables.net-bs5" "~10.1.0-rc.1" - "@abp/font-awesome" "~10.1.0-rc.1" - "@abp/jquery-form" "~10.1.0-rc.1" - "@abp/jquery-validation-unobtrusive" "~10.1.0-rc.1" - "@abp/lodash" "~10.1.0-rc.1" - "@abp/luxon" "~10.1.0-rc.1" - "@abp/malihu-custom-scrollbar-plugin" "~10.1.0-rc.1" - "@abp/moment" "~10.1.0-rc.1" - "@abp/select2" "~10.1.0-rc.1" - "@abp/sweetalert2" "~10.1.0-rc.1" - "@abp/timeago" "~10.1.0-rc.1" - -"@abp/aspnetcore.mvc.ui@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.1.0-rc.1.tgz#290297353fdc15826a870b35162a0bbf4c9bc948" - integrity sha512-RBR5wPf5ygzuSpfXG5MeOGd/YOtQH5pVyU65T4DMtGpLKHQxgxbh7JK1btyC5zGmzr/Ds5+5Mcd7S66dIckDJQ== + "@abp/aspnetcore.mvc.ui" "~10.1.0-rc.2" + "@abp/bootstrap" "~10.1.0-rc.2" + "@abp/bootstrap-datepicker" "~10.1.0-rc.2" + "@abp/bootstrap-daterangepicker" "~10.1.0-rc.2" + "@abp/datatables.net-bs5" "~10.1.0-rc.2" + "@abp/font-awesome" "~10.1.0-rc.2" + "@abp/jquery-form" "~10.1.0-rc.2" + "@abp/jquery-validation-unobtrusive" "~10.1.0-rc.2" + "@abp/lodash" "~10.1.0-rc.2" + "@abp/luxon" "~10.1.0-rc.2" + "@abp/malihu-custom-scrollbar-plugin" "~10.1.0-rc.2" + "@abp/moment" "~10.1.0-rc.2" + "@abp/select2" "~10.1.0-rc.2" + "@abp/sweetalert2" "~10.1.0-rc.2" + "@abp/timeago" "~10.1.0-rc.2" + +"@abp/aspnetcore.mvc.ui@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.1.0-rc.2.tgz#e25d3575d40bfcb3f809bd2d355671181ee5ff40" + integrity sha512-MOF86bVbi7N/nIla+361nsBrN4tiSka8xzpWcgqlLcCAl9ILG4rugbtafBAjN81taPma2peZM7egaOR4SDkTMw== dependencies: ansi-colors "^4.1.3" -"@abp/bootstrap-datepicker@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.1.0-rc.1.tgz#54730dc001dab746af18c41d51b73949742e3b2d" - integrity sha512-DhVfA9WjfRQrUwjN2eJTCjLEhAU7rLjWgMlifW1id3HbdMs9emKn0rzQqhZ8MOSUNBwLkbUWcXWnojRUFgUB+w== +"@abp/bootstrap-datepicker@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.1.0-rc.2.tgz#be80c6104ba53e18935fbf62ca2c1890f4b2fde4" + integrity sha512-BNcDYUSbZaLah4SfXm0efoqFTsOViVm6370k9L7vix/OGpIWwklJsr8y78lvdM5ANgNCfl0LPSq+seLJFc/OLA== dependencies: bootstrap-datepicker "^1.10.1" -"@abp/bootstrap-daterangepicker@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.1.0-rc.1.tgz#9af7ffe1d1bd4fc99e1606e30189e6f28bb2f201" - integrity sha512-/Hsge8UwLZFuPNWRZhpf4nVB4urPYsaKAuCz0pSH5OHmS4FWr0Ng3vnUd9fcSeaq9OV2HarcgGWDapYtszttVQ== +"@abp/bootstrap-daterangepicker@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.1.0-rc.2.tgz#f189f7d070ebd97d9cfdcb99571cab2d6a198ab5" + integrity sha512-bV8J0MuiAFVLkr48JsB6aZU6aPoqw+Gyhq1szQ74bEwNQlRBPuF92WVA5FACaUBj8dMUzR9HDDAYQuxUzpKYKA== dependencies: bootstrap-daterangepicker "^3.1.0" -"@abp/bootstrap@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.1.0-rc.1.tgz#e937f0a10874c0f3b0f719ff6a316b0ca91e65c5" - integrity sha512-GlRyH5oCjcmFk1eBGCHk6GglL2DJ5RLlDXn7HWU675u48DCQTkBbKP9qA3RcYERQB4qqhcOKEDtYPZVljRVHRQ== +"@abp/bootstrap@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.1.0-rc.2.tgz#2300800a29ea09b91f5ed2e6177e5921fe7d2a0f" + integrity sha512-K+tDI9vz/Y9B/yu0i3AVpm4v3Odi44Q/yH5hAprL7f4pGxEOiqAFB/qzHAxG+7Oa7wjv5tPLv+Cz4DavBQjd8Q== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" bootstrap "^5.3.8" -"@abp/core@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.1.0-rc.1.tgz#d9ce0958407fa39c8015eb2d9328a3a157aaa4f0" - integrity sha512-pFsaQei1W0JRJkhNdlxq+EDP7x+83nT0UHZetTHNzp8tkP4Cn8Ni/ZODIUku8ck1vcKc0X3pXKj42BN+yZ1g3g== +"@abp/core@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.1.0-rc.2.tgz#403687aff5a30788f7b7ca660abdfd85d89438aa" + integrity sha512-euuG2Hna/DT6/R1dGOjgp3vcehYtF+CcOkRj31oquYKaM5YWk4OaZ314DSpnjgs/xo8DuVc4eKFQwIxD9RK41w== dependencies: - "@abp/utils" "~10.1.0-rc.1" + "@abp/utils" "~10.1.0-rc.2" -"@abp/datatables.net-bs5@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.1.0-rc.1.tgz#7981bc64c543d24aa5b0ab373f5693187990d5be" - integrity sha512-yaYHuZ9eVHT2cZcH9r680X+imWhOqbhWlOlbLDpJtg7Un0ejDzou+JqxKYHHXmSEKYEPHjfCEhnXkMnyqvSUHQ== +"@abp/datatables.net-bs5@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.1.0-rc.2.tgz#a60650d1802b40751d30f8f6c56beb23fd66481b" + integrity sha512-IWwexNqbMpET54Fvm9LoPTJYf+4CoBbjFOvz3sL6CgO2feV5R5fKigjVU8zXKNh2W+RG8L6zEarfVxrr114TsA== dependencies: - "@abp/datatables.net" "~10.1.0-rc.1" + "@abp/datatables.net" "~10.1.0-rc.2" datatables.net-bs5 "^2.3.4" -"@abp/datatables.net@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.1.0-rc.1.tgz#f487357690060dd055dbb7f54e1298e8d8a81e40" - integrity sha512-KTKTW73H9vaX6J9ipXUf7d3QdseKaFRzl7e1QKldOoQxBs4pzT2NATGdUyiAnJsfPl4scpSFetzI66Rs7r3idg== +"@abp/datatables.net@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.1.0-rc.2.tgz#9147f68bc6dbc4eb40a9ddf65c7859e788cdcac2" + integrity sha512-a9DJpwg14S4nVOiC4ipw0CQwEYWB602e2gCJiH7W1mxopbQb135RxwhtdTnW//eIONcxC9IrEuvcBEAUVt2B7Q== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" datatables.net "^2.3.4" -"@abp/font-awesome@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.1.0-rc.1.tgz#2ee67b07ce6296c7f0ac5217c9d9677b9c70f05e" - integrity sha512-O/3oLbSu1pMNftQzQW29gSpkWCFJazFKjT7sOYId1Ueric0svyJ42hFr5gHf9RFVmCU5pgyhawcoTyzQ34PxYQ== +"@abp/font-awesome@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.1.0-rc.2.tgz#364466cfe67e41e0c4d16b57d3923d10f66369f1" + integrity sha512-F1Jy8xoFV2aA+VN+NH1gtrG96/j9w7Picc+KLoCoIyNnJr/xJur11XkJyu5ln8KF4V7p/DY7QaQodWV/btOs8g== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" "@fortawesome/fontawesome-free" "^7.0.1" -"@abp/jquery-form@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-10.1.0-rc.1.tgz#60fc98085671a2ff08e8c411847354b6a48cf55e" - integrity sha512-bj70hE7MsH2AoBb2yU2d6ng5Bo/wqjCNUI0kbG0T1T4yp1cToGAVJ6hrd+KOp2Tf6W+NITn1EycxRTCktE9QRA== +"@abp/jquery-form@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-10.1.0-rc.2.tgz#3857717d07569c22d4bbbe459238abeb816d606a" + integrity sha512-2D5WHVnfK9bhRces1tgPwOEoc7KCYKYiKHBOcqct+LTA7zoRjJv/PM8/JhFVl+grVIw1aSwO4tU3YfZ22Vxipg== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" jquery-form "^4.3.0" -"@abp/jquery-validation-unobtrusive@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.1.0-rc.1.tgz#a820989d8daf53096f838817cb2b3dba21f71653" - integrity sha512-7uXxbvuZ+ecD5suH3nx/ggiFAA8GjXGAB5tuGCdqePdBn1PMzDIDI1ZP9q8sR7sMjo7RKVK6MzX+PazUuJ4L7g== +"@abp/jquery-validation-unobtrusive@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.1.0-rc.2.tgz#efd7b69a078a20c0bf405408dbdf52a7bf770b3b" + integrity sha512-tZ0MWgzBqp+SNfMxM0z2cGB21NiTHuVJyyQaXKE/ptuD5pc0uRkcqw/J2kWfiqsoVgChz27IB6h8/jqDafS4qg== dependencies: - "@abp/jquery-validation" "~10.1.0-rc.1" + "@abp/jquery-validation" "~10.1.0-rc.2" jquery-validation-unobtrusive "^4.0.0" -"@abp/jquery-validation@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.1.0-rc.1.tgz#c8630b0a53ee93218be5c82264085b36b0b07936" - integrity sha512-/eS4vy5gwHIaRr6aHkvO6xWRjL22bVly7S0AqbZw2OxJ0pw1n23n+zipn/NDRSngRZSajTLLm1pYCG5qlJGd3A== +"@abp/jquery-validation@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.1.0-rc.2.tgz#d39537a7356c51f9db2e66f6740cf6df86bd0442" + integrity sha512-LOkS0NKk4pLtLjPU0CCbwROyUg6EtJN8Z/it7QuKK1CIRfYYcAStgNnNm5geZP7CqECIkoiFfgWjI+L5Z9/Tfg== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" jquery-validation "^1.21.0" -"@abp/jquery@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.1.0-rc.1.tgz#38d5d5b5a6144bf207773ef26f36d381a257a635" - integrity sha512-YarNlYPG7MfLc0fSnOcVrp9Da1EUxz4zUERnTOQ0/EdOUdcFhGAqI2yfoTFCxqX+TSphQWZEdif/tsfULtkI1A== +"@abp/jquery@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.1.0-rc.2.tgz#101a55f70d510978c8c05f5857d0e9d4965263f7" + integrity sha512-bQV1uFWGtwRYjNOsqJ8FM2004idX2Jj7YVL19YF1/PjyPUSMX+s8/IvJizBjyY5hPAiWBBhmV9g+IFWzxlDQoQ== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" jquery "~3.7.1" -"@abp/lodash@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.1.0-rc.1.tgz#bf735e34a42ddcdf19e331c8c5cf1a24a87475cb" - integrity sha512-JQV0oFLE90V9GdrD37PTMiOylw/U6yjpDu5PblVW/zlNmw/TzEtukp3/vJl7GJxoKRYWtgJf1nFLgG/+2kjE1A== +"@abp/lodash@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.1.0-rc.2.tgz#d08c03f8d3d0fbaa3e71e603cbe5fb7f176933ef" + integrity sha512-KCnD1p2y52ZI+2ifpiFIUAiDPsKehnOD8HV5qKeObO6UCP97okif8IP+sQDmNQb8O33y/NKTyx/HcpwBbe/NYQ== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" lodash "^4.17.21" -"@abp/luxon@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.1.0-rc.1.tgz#1c883ad3a5653f0c6ce4965e4b4ce21543129534" - integrity sha512-SDtwjIDR4OzrtzRWpgb8atUSK0DwajJ5LC486xHq5ftnbFETRfOwihyIfC2H4oIPPDE5oS+LOnjCkRYVkGt0oQ== +"@abp/luxon@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.1.0-rc.2.tgz#ef8d2b323bac054fc9610e241e1b1763d229e065" + integrity sha512-qYFl6XO3g9mZiu0dtIczI7LRuYWwc+RkpbDzSmruXcRks3KA+ZZco2vhHNnlwtXcINl/TXtbW7Wc0MX+8IB1Kw== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" luxon "^3.7.2" -"@abp/malihu-custom-scrollbar-plugin@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.1.0-rc.1.tgz#770106795f5eba9d36dd2d4002e450c215c66787" - integrity sha512-EFwQ7rUiPM6mxznn1t23ySzQH+VQ0dB2YxOqBAluFR/yC3qypjb98jGQaZKsJx9+iT4DuG9gGPKOfct1Sqh0KQ== +"@abp/malihu-custom-scrollbar-plugin@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.1.0-rc.2.tgz#dfaf666442c7c122f7da72c83b9adf194d5b6ec8" + integrity sha512-PudMHmNQgZ6JZeaVt1ZoXLqO0UZXJzUYiBah2LDkC4EMLjnMJFINHBoEVVa4ooXH0yjFv+zsbN0vWZYJ8TBJIA== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/moment@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.1.0-rc.1.tgz#fe5bfa5bc28bb847cf801523c1b2f118ffb12447" - integrity sha512-P6z9YO/FC/5DhTDWsHkquo5tEzIhSfUnRefLcZtv7XuJtevH/93NFSr23RL6D+F4r3cB5GMXGFUYmXGKWoXGHg== +"@abp/moment@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.1.0-rc.2.tgz#610a1592d13984aea51abbd13df8c5995a089149" + integrity sha512-ep8PnAXARw0t/wtGOVp/oiNhF3B0Bh6y2vRzKrcSoyXAQREGGm4fJdZVYZLGTfI4lFLTjebEgf4O7T9feUwJAw== dependencies: moment "^2.30.1" -"@abp/select2@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.1.0-rc.1.tgz#19432bb6483f8a3470f02a3037612835b4c54ef4" - integrity sha512-xPvdrgXZzRhEIrEyw9eoITc+hYlbi8fSW3BGQ1ZMBXWgYkjWxhj/1AuLsu2ewFAKdBNz+PewjA0MKP6iBLATHw== +"@abp/select2@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.1.0-rc.2.tgz#40c5418d007fc36817eecbe6388d767e4e7ca887" + integrity sha512-Pq0wlpL01sWRLUg5um3JtBXIqi3mmbwPwvgxP8hFbQngAt9JXAK8geNRiTMrIZgtW/ycXtM1v6I4zuWOLOeAGg== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" select2 "^4.0.13" -"@abp/sweetalert2@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.1.0-rc.1.tgz#40f4d6964cd6620dcb81040d84e432f4ae314686" - integrity sha512-E4dUC2k/kClBnadtGK5NWR9YmCGgKq5uOGjEVtzXxJH62MlaCXlcxRrK9wNzX8pzDMDYFKjuOUugoROEgbRKWQ== +"@abp/sweetalert2@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.1.0-rc.2.tgz#d35858c69e10c6726b02cdfcea88dfc32385963f" + integrity sha512-s9VPRToohN45uzHcKCF5Mcj8FVjsXcXUb0U3tuaT/Y+u4adHB3fBxYiXJFM0sVsCJ81dFktxwka40Wm8Taz/zA== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" sweetalert2 "^11.23.0" -"@abp/timeago@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.1.0-rc.1.tgz#482e3a645f63ee9adf708ef8ae895fcf2bcb4271" - integrity sha512-UOb2o6bvi3+2kbD7+rUJdqTQvTbgqUD7ktd0U5Hulb3dBOyv6MZEagzMbXA003OROiS23JzzT7M0lLStvixwlg== +"@abp/timeago@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.1.0-rc.2.tgz#98d630cc3843eee64dbcc34fb8ca5afbab034718" + integrity sha512-vJmk+otyXXJE2s2J8iYpLVaFuNAYnIUSOitmi7umYnL+k/UE2KQhBXU7FR0/OBY9mAZYd+shaiGIU1LMSaJ+Xg== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" timeago "^1.6.7" -"@abp/utils@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.1.0-rc.1.tgz#e53ef81f39fd620b41ae2bfb69d06ee85ccb28ad" - integrity sha512-xBHIw5rmAttN5PL5C84L2znHkA2HMrP6HaA2aV5k6KU7BtOWJhviZnKJgbUBfufNQNeLwlThCHAfS7aOv/UQkg== +"@abp/utils@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.1.0-rc.2.tgz#86a980c6536b3b5ce185d406723b28be421864ac" + integrity sha512-Oz863VNA8fraQ81vTvqM0IqwiaseLwfFU5QNn6iOGOfn5wQrEkPwtZ0jMI+DGNtJgPzoKiq+iKc3K+SiuVgldg== dependencies: just-compare "^2.3.0" diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Host/package.json b/modules/cms-kit/host/Volo.CmsKit.Web.Host/package.json index 881359366e..52a1fc6c21 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Host/package.json +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Host/package.json @@ -3,6 +3,6 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~10.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.basic": "~10.1.0-rc.2" } } diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Host/yarn.lock b/modules/cms-kit/host/Volo.CmsKit.Web.Host/yarn.lock index 55a960f9ab..a2b6e13b29 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Host/yarn.lock +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Host/yarn.lock @@ -2,185 +2,185 @@ # yarn lockfile v1 -"@abp/aspnetcore.mvc.ui.theme.basic@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-10.1.0-rc.1.tgz#99407c4108b685eef91f21d038cc5bd2343b5847" - integrity sha512-UbVgYf1K+zUqueNCwOl4QoUAz7nwJyoQJSqq0gi7LA9JBRBbb8nDA8szQjAUVqF4h+k4o61588MUQnerUQlZ6g== +"@abp/aspnetcore.mvc.ui.theme.basic@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-10.1.0-rc.2.tgz#599f5c47a417d1230fc17c0446a0229f920f7246" + integrity sha512-8F4nEK+VtgRRf8n+66HMbtCEaOMCW/OdbSEWRl9ahMNoj860oPIJ8P8Qn/2+LjtkPMdDAfCdEzyDzCd3igaFaA== dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~10.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.shared" "~10.1.0-rc.2" -"@abp/aspnetcore.mvc.ui.theme.shared@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.1.0-rc.1.tgz#7c0f721bdd4ec99f441a49e662a457d98c2c66fa" - integrity sha512-dB6wmR3C43Vn1/hiWY3IkxivUEWTGhO4mj3hffwAeyJqnEUh6kQUW6UfmYNGEhahSjhVIS3lUZ4x+R+yGCpemw== +"@abp/aspnetcore.mvc.ui.theme.shared@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.1.0-rc.2.tgz#e5056e4e159f5815e3cffecab5c46f3d7d4f79d7" + integrity sha512-bo56XzQZPYL/3ckWTTTSSUsSFSFJobvfE29cz13NIrZ/tBtWyQCAJn92wYHuY+6IezYUWb4ga3PkFeHRzR142A== dependencies: - "@abp/aspnetcore.mvc.ui" "~10.1.0-rc.1" - "@abp/bootstrap" "~10.1.0-rc.1" - "@abp/bootstrap-datepicker" "~10.1.0-rc.1" - "@abp/bootstrap-daterangepicker" "~10.1.0-rc.1" - "@abp/datatables.net-bs5" "~10.1.0-rc.1" - "@abp/font-awesome" "~10.1.0-rc.1" - "@abp/jquery-form" "~10.1.0-rc.1" - "@abp/jquery-validation-unobtrusive" "~10.1.0-rc.1" - "@abp/lodash" "~10.1.0-rc.1" - "@abp/luxon" "~10.1.0-rc.1" - "@abp/malihu-custom-scrollbar-plugin" "~10.1.0-rc.1" - "@abp/moment" "~10.1.0-rc.1" - "@abp/select2" "~10.1.0-rc.1" - "@abp/sweetalert2" "~10.1.0-rc.1" - "@abp/timeago" "~10.1.0-rc.1" - -"@abp/aspnetcore.mvc.ui@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.1.0-rc.1.tgz#290297353fdc15826a870b35162a0bbf4c9bc948" - integrity sha512-RBR5wPf5ygzuSpfXG5MeOGd/YOtQH5pVyU65T4DMtGpLKHQxgxbh7JK1btyC5zGmzr/Ds5+5Mcd7S66dIckDJQ== + "@abp/aspnetcore.mvc.ui" "~10.1.0-rc.2" + "@abp/bootstrap" "~10.1.0-rc.2" + "@abp/bootstrap-datepicker" "~10.1.0-rc.2" + "@abp/bootstrap-daterangepicker" "~10.1.0-rc.2" + "@abp/datatables.net-bs5" "~10.1.0-rc.2" + "@abp/font-awesome" "~10.1.0-rc.2" + "@abp/jquery-form" "~10.1.0-rc.2" + "@abp/jquery-validation-unobtrusive" "~10.1.0-rc.2" + "@abp/lodash" "~10.1.0-rc.2" + "@abp/luxon" "~10.1.0-rc.2" + "@abp/malihu-custom-scrollbar-plugin" "~10.1.0-rc.2" + "@abp/moment" "~10.1.0-rc.2" + "@abp/select2" "~10.1.0-rc.2" + "@abp/sweetalert2" "~10.1.0-rc.2" + "@abp/timeago" "~10.1.0-rc.2" + +"@abp/aspnetcore.mvc.ui@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.1.0-rc.2.tgz#e25d3575d40bfcb3f809bd2d355671181ee5ff40" + integrity sha512-MOF86bVbi7N/nIla+361nsBrN4tiSka8xzpWcgqlLcCAl9ILG4rugbtafBAjN81taPma2peZM7egaOR4SDkTMw== dependencies: ansi-colors "^4.1.3" -"@abp/bootstrap-datepicker@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.1.0-rc.1.tgz#54730dc001dab746af18c41d51b73949742e3b2d" - integrity sha512-DhVfA9WjfRQrUwjN2eJTCjLEhAU7rLjWgMlifW1id3HbdMs9emKn0rzQqhZ8MOSUNBwLkbUWcXWnojRUFgUB+w== +"@abp/bootstrap-datepicker@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.1.0-rc.2.tgz#be80c6104ba53e18935fbf62ca2c1890f4b2fde4" + integrity sha512-BNcDYUSbZaLah4SfXm0efoqFTsOViVm6370k9L7vix/OGpIWwklJsr8y78lvdM5ANgNCfl0LPSq+seLJFc/OLA== dependencies: bootstrap-datepicker "^1.10.1" -"@abp/bootstrap-daterangepicker@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.1.0-rc.1.tgz#9af7ffe1d1bd4fc99e1606e30189e6f28bb2f201" - integrity sha512-/Hsge8UwLZFuPNWRZhpf4nVB4urPYsaKAuCz0pSH5OHmS4FWr0Ng3vnUd9fcSeaq9OV2HarcgGWDapYtszttVQ== +"@abp/bootstrap-daterangepicker@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.1.0-rc.2.tgz#f189f7d070ebd97d9cfdcb99571cab2d6a198ab5" + integrity sha512-bV8J0MuiAFVLkr48JsB6aZU6aPoqw+Gyhq1szQ74bEwNQlRBPuF92WVA5FACaUBj8dMUzR9HDDAYQuxUzpKYKA== dependencies: bootstrap-daterangepicker "^3.1.0" -"@abp/bootstrap@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.1.0-rc.1.tgz#e937f0a10874c0f3b0f719ff6a316b0ca91e65c5" - integrity sha512-GlRyH5oCjcmFk1eBGCHk6GglL2DJ5RLlDXn7HWU675u48DCQTkBbKP9qA3RcYERQB4qqhcOKEDtYPZVljRVHRQ== +"@abp/bootstrap@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.1.0-rc.2.tgz#2300800a29ea09b91f5ed2e6177e5921fe7d2a0f" + integrity sha512-K+tDI9vz/Y9B/yu0i3AVpm4v3Odi44Q/yH5hAprL7f4pGxEOiqAFB/qzHAxG+7Oa7wjv5tPLv+Cz4DavBQjd8Q== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" bootstrap "^5.3.8" -"@abp/core@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.1.0-rc.1.tgz#d9ce0958407fa39c8015eb2d9328a3a157aaa4f0" - integrity sha512-pFsaQei1W0JRJkhNdlxq+EDP7x+83nT0UHZetTHNzp8tkP4Cn8Ni/ZODIUku8ck1vcKc0X3pXKj42BN+yZ1g3g== +"@abp/core@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.1.0-rc.2.tgz#403687aff5a30788f7b7ca660abdfd85d89438aa" + integrity sha512-euuG2Hna/DT6/R1dGOjgp3vcehYtF+CcOkRj31oquYKaM5YWk4OaZ314DSpnjgs/xo8DuVc4eKFQwIxD9RK41w== dependencies: - "@abp/utils" "~10.1.0-rc.1" + "@abp/utils" "~10.1.0-rc.2" -"@abp/datatables.net-bs5@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.1.0-rc.1.tgz#7981bc64c543d24aa5b0ab373f5693187990d5be" - integrity sha512-yaYHuZ9eVHT2cZcH9r680X+imWhOqbhWlOlbLDpJtg7Un0ejDzou+JqxKYHHXmSEKYEPHjfCEhnXkMnyqvSUHQ== +"@abp/datatables.net-bs5@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.1.0-rc.2.tgz#a60650d1802b40751d30f8f6c56beb23fd66481b" + integrity sha512-IWwexNqbMpET54Fvm9LoPTJYf+4CoBbjFOvz3sL6CgO2feV5R5fKigjVU8zXKNh2W+RG8L6zEarfVxrr114TsA== dependencies: - "@abp/datatables.net" "~10.1.0-rc.1" + "@abp/datatables.net" "~10.1.0-rc.2" datatables.net-bs5 "^2.3.4" -"@abp/datatables.net@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.1.0-rc.1.tgz#f487357690060dd055dbb7f54e1298e8d8a81e40" - integrity sha512-KTKTW73H9vaX6J9ipXUf7d3QdseKaFRzl7e1QKldOoQxBs4pzT2NATGdUyiAnJsfPl4scpSFetzI66Rs7r3idg== +"@abp/datatables.net@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.1.0-rc.2.tgz#9147f68bc6dbc4eb40a9ddf65c7859e788cdcac2" + integrity sha512-a9DJpwg14S4nVOiC4ipw0CQwEYWB602e2gCJiH7W1mxopbQb135RxwhtdTnW//eIONcxC9IrEuvcBEAUVt2B7Q== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" datatables.net "^2.3.4" -"@abp/font-awesome@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.1.0-rc.1.tgz#2ee67b07ce6296c7f0ac5217c9d9677b9c70f05e" - integrity sha512-O/3oLbSu1pMNftQzQW29gSpkWCFJazFKjT7sOYId1Ueric0svyJ42hFr5gHf9RFVmCU5pgyhawcoTyzQ34PxYQ== +"@abp/font-awesome@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.1.0-rc.2.tgz#364466cfe67e41e0c4d16b57d3923d10f66369f1" + integrity sha512-F1Jy8xoFV2aA+VN+NH1gtrG96/j9w7Picc+KLoCoIyNnJr/xJur11XkJyu5ln8KF4V7p/DY7QaQodWV/btOs8g== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" "@fortawesome/fontawesome-free" "^7.0.1" -"@abp/jquery-form@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-10.1.0-rc.1.tgz#60fc98085671a2ff08e8c411847354b6a48cf55e" - integrity sha512-bj70hE7MsH2AoBb2yU2d6ng5Bo/wqjCNUI0kbG0T1T4yp1cToGAVJ6hrd+KOp2Tf6W+NITn1EycxRTCktE9QRA== +"@abp/jquery-form@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-10.1.0-rc.2.tgz#3857717d07569c22d4bbbe459238abeb816d606a" + integrity sha512-2D5WHVnfK9bhRces1tgPwOEoc7KCYKYiKHBOcqct+LTA7zoRjJv/PM8/JhFVl+grVIw1aSwO4tU3YfZ22Vxipg== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" jquery-form "^4.3.0" -"@abp/jquery-validation-unobtrusive@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.1.0-rc.1.tgz#a820989d8daf53096f838817cb2b3dba21f71653" - integrity sha512-7uXxbvuZ+ecD5suH3nx/ggiFAA8GjXGAB5tuGCdqePdBn1PMzDIDI1ZP9q8sR7sMjo7RKVK6MzX+PazUuJ4L7g== +"@abp/jquery-validation-unobtrusive@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.1.0-rc.2.tgz#efd7b69a078a20c0bf405408dbdf52a7bf770b3b" + integrity sha512-tZ0MWgzBqp+SNfMxM0z2cGB21NiTHuVJyyQaXKE/ptuD5pc0uRkcqw/J2kWfiqsoVgChz27IB6h8/jqDafS4qg== dependencies: - "@abp/jquery-validation" "~10.1.0-rc.1" + "@abp/jquery-validation" "~10.1.0-rc.2" jquery-validation-unobtrusive "^4.0.0" -"@abp/jquery-validation@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.1.0-rc.1.tgz#c8630b0a53ee93218be5c82264085b36b0b07936" - integrity sha512-/eS4vy5gwHIaRr6aHkvO6xWRjL22bVly7S0AqbZw2OxJ0pw1n23n+zipn/NDRSngRZSajTLLm1pYCG5qlJGd3A== +"@abp/jquery-validation@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.1.0-rc.2.tgz#d39537a7356c51f9db2e66f6740cf6df86bd0442" + integrity sha512-LOkS0NKk4pLtLjPU0CCbwROyUg6EtJN8Z/it7QuKK1CIRfYYcAStgNnNm5geZP7CqECIkoiFfgWjI+L5Z9/Tfg== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" jquery-validation "^1.21.0" -"@abp/jquery@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.1.0-rc.1.tgz#38d5d5b5a6144bf207773ef26f36d381a257a635" - integrity sha512-YarNlYPG7MfLc0fSnOcVrp9Da1EUxz4zUERnTOQ0/EdOUdcFhGAqI2yfoTFCxqX+TSphQWZEdif/tsfULtkI1A== +"@abp/jquery@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.1.0-rc.2.tgz#101a55f70d510978c8c05f5857d0e9d4965263f7" + integrity sha512-bQV1uFWGtwRYjNOsqJ8FM2004idX2Jj7YVL19YF1/PjyPUSMX+s8/IvJizBjyY5hPAiWBBhmV9g+IFWzxlDQoQ== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" jquery "~3.7.1" -"@abp/lodash@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.1.0-rc.1.tgz#bf735e34a42ddcdf19e331c8c5cf1a24a87475cb" - integrity sha512-JQV0oFLE90V9GdrD37PTMiOylw/U6yjpDu5PblVW/zlNmw/TzEtukp3/vJl7GJxoKRYWtgJf1nFLgG/+2kjE1A== +"@abp/lodash@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.1.0-rc.2.tgz#d08c03f8d3d0fbaa3e71e603cbe5fb7f176933ef" + integrity sha512-KCnD1p2y52ZI+2ifpiFIUAiDPsKehnOD8HV5qKeObO6UCP97okif8IP+sQDmNQb8O33y/NKTyx/HcpwBbe/NYQ== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" lodash "^4.17.21" -"@abp/luxon@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.1.0-rc.1.tgz#1c883ad3a5653f0c6ce4965e4b4ce21543129534" - integrity sha512-SDtwjIDR4OzrtzRWpgb8atUSK0DwajJ5LC486xHq5ftnbFETRfOwihyIfC2H4oIPPDE5oS+LOnjCkRYVkGt0oQ== +"@abp/luxon@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.1.0-rc.2.tgz#ef8d2b323bac054fc9610e241e1b1763d229e065" + integrity sha512-qYFl6XO3g9mZiu0dtIczI7LRuYWwc+RkpbDzSmruXcRks3KA+ZZco2vhHNnlwtXcINl/TXtbW7Wc0MX+8IB1Kw== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" luxon "^3.7.2" -"@abp/malihu-custom-scrollbar-plugin@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.1.0-rc.1.tgz#770106795f5eba9d36dd2d4002e450c215c66787" - integrity sha512-EFwQ7rUiPM6mxznn1t23ySzQH+VQ0dB2YxOqBAluFR/yC3qypjb98jGQaZKsJx9+iT4DuG9gGPKOfct1Sqh0KQ== +"@abp/malihu-custom-scrollbar-plugin@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.1.0-rc.2.tgz#dfaf666442c7c122f7da72c83b9adf194d5b6ec8" + integrity sha512-PudMHmNQgZ6JZeaVt1ZoXLqO0UZXJzUYiBah2LDkC4EMLjnMJFINHBoEVVa4ooXH0yjFv+zsbN0vWZYJ8TBJIA== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/moment@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.1.0-rc.1.tgz#fe5bfa5bc28bb847cf801523c1b2f118ffb12447" - integrity sha512-P6z9YO/FC/5DhTDWsHkquo5tEzIhSfUnRefLcZtv7XuJtevH/93NFSr23RL6D+F4r3cB5GMXGFUYmXGKWoXGHg== +"@abp/moment@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.1.0-rc.2.tgz#610a1592d13984aea51abbd13df8c5995a089149" + integrity sha512-ep8PnAXARw0t/wtGOVp/oiNhF3B0Bh6y2vRzKrcSoyXAQREGGm4fJdZVYZLGTfI4lFLTjebEgf4O7T9feUwJAw== dependencies: moment "^2.30.1" -"@abp/select2@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.1.0-rc.1.tgz#19432bb6483f8a3470f02a3037612835b4c54ef4" - integrity sha512-xPvdrgXZzRhEIrEyw9eoITc+hYlbi8fSW3BGQ1ZMBXWgYkjWxhj/1AuLsu2ewFAKdBNz+PewjA0MKP6iBLATHw== +"@abp/select2@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.1.0-rc.2.tgz#40c5418d007fc36817eecbe6388d767e4e7ca887" + integrity sha512-Pq0wlpL01sWRLUg5um3JtBXIqi3mmbwPwvgxP8hFbQngAt9JXAK8geNRiTMrIZgtW/ycXtM1v6I4zuWOLOeAGg== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" select2 "^4.0.13" -"@abp/sweetalert2@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.1.0-rc.1.tgz#40f4d6964cd6620dcb81040d84e432f4ae314686" - integrity sha512-E4dUC2k/kClBnadtGK5NWR9YmCGgKq5uOGjEVtzXxJH62MlaCXlcxRrK9wNzX8pzDMDYFKjuOUugoROEgbRKWQ== +"@abp/sweetalert2@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.1.0-rc.2.tgz#d35858c69e10c6726b02cdfcea88dfc32385963f" + integrity sha512-s9VPRToohN45uzHcKCF5Mcj8FVjsXcXUb0U3tuaT/Y+u4adHB3fBxYiXJFM0sVsCJ81dFktxwka40Wm8Taz/zA== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" sweetalert2 "^11.23.0" -"@abp/timeago@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.1.0-rc.1.tgz#482e3a645f63ee9adf708ef8ae895fcf2bcb4271" - integrity sha512-UOb2o6bvi3+2kbD7+rUJdqTQvTbgqUD7ktd0U5Hulb3dBOyv6MZEagzMbXA003OROiS23JzzT7M0lLStvixwlg== +"@abp/timeago@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.1.0-rc.2.tgz#98d630cc3843eee64dbcc34fb8ca5afbab034718" + integrity sha512-vJmk+otyXXJE2s2J8iYpLVaFuNAYnIUSOitmi7umYnL+k/UE2KQhBXU7FR0/OBY9mAZYd+shaiGIU1LMSaJ+Xg== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" timeago "^1.6.7" -"@abp/utils@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.1.0-rc.1.tgz#e53ef81f39fd620b41ae2bfb69d06ee85ccb28ad" - integrity sha512-xBHIw5rmAttN5PL5C84L2znHkA2HMrP6HaA2aV5k6KU7BtOWJhviZnKJgbUBfufNQNeLwlThCHAfS7aOv/UQkg== +"@abp/utils@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.1.0-rc.2.tgz#86a980c6536b3b5ce185d406723b28be421864ac" + integrity sha512-Oz863VNA8fraQ81vTvqM0IqwiaseLwfFU5QNn6iOGOfn5wQrEkPwtZ0jMI+DGNtJgPzoKiq+iKc3K+SiuVgldg== dependencies: just-compare "^2.3.0" diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/package.json b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/package.json index 3bfc3b1e83..882508c4e3 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/package.json +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/package.json @@ -3,7 +3,7 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~10.1.0-rc.1", - "@abp/cms-kit": "10.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.basic": "~10.1.0-rc.2", + "@abp/cms-kit": "10.1.0-rc.2" } } diff --git a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/yarn.lock b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/yarn.lock index ffae765b95..97b79e0912 100644 --- a/modules/cms-kit/host/Volo.CmsKit.Web.Unified/yarn.lock +++ b/modules/cms-kit/host/Volo.CmsKit.Web.Unified/yarn.lock @@ -2,293 +2,293 @@ # yarn lockfile v1 -"@abp/aspnetcore.mvc.ui.theme.basic@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-10.1.0-rc.1.tgz#99407c4108b685eef91f21d038cc5bd2343b5847" - integrity sha512-UbVgYf1K+zUqueNCwOl4QoUAz7nwJyoQJSqq0gi7LA9JBRBbb8nDA8szQjAUVqF4h+k4o61588MUQnerUQlZ6g== - dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~10.1.0-rc.1" - -"@abp/aspnetcore.mvc.ui.theme.shared@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.1.0-rc.1.tgz#7c0f721bdd4ec99f441a49e662a457d98c2c66fa" - integrity sha512-dB6wmR3C43Vn1/hiWY3IkxivUEWTGhO4mj3hffwAeyJqnEUh6kQUW6UfmYNGEhahSjhVIS3lUZ4x+R+yGCpemw== - dependencies: - "@abp/aspnetcore.mvc.ui" "~10.1.0-rc.1" - "@abp/bootstrap" "~10.1.0-rc.1" - "@abp/bootstrap-datepicker" "~10.1.0-rc.1" - "@abp/bootstrap-daterangepicker" "~10.1.0-rc.1" - "@abp/datatables.net-bs5" "~10.1.0-rc.1" - "@abp/font-awesome" "~10.1.0-rc.1" - "@abp/jquery-form" "~10.1.0-rc.1" - "@abp/jquery-validation-unobtrusive" "~10.1.0-rc.1" - "@abp/lodash" "~10.1.0-rc.1" - "@abp/luxon" "~10.1.0-rc.1" - "@abp/malihu-custom-scrollbar-plugin" "~10.1.0-rc.1" - "@abp/moment" "~10.1.0-rc.1" - "@abp/select2" "~10.1.0-rc.1" - "@abp/sweetalert2" "~10.1.0-rc.1" - "@abp/timeago" "~10.1.0-rc.1" - -"@abp/aspnetcore.mvc.ui@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.1.0-rc.1.tgz#290297353fdc15826a870b35162a0bbf4c9bc948" - integrity sha512-RBR5wPf5ygzuSpfXG5MeOGd/YOtQH5pVyU65T4DMtGpLKHQxgxbh7JK1btyC5zGmzr/Ds5+5Mcd7S66dIckDJQ== +"@abp/aspnetcore.mvc.ui.theme.basic@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-10.1.0-rc.2.tgz#599f5c47a417d1230fc17c0446a0229f920f7246" + integrity sha512-8F4nEK+VtgRRf8n+66HMbtCEaOMCW/OdbSEWRl9ahMNoj860oPIJ8P8Qn/2+LjtkPMdDAfCdEzyDzCd3igaFaA== + dependencies: + "@abp/aspnetcore.mvc.ui.theme.shared" "~10.1.0-rc.2" + +"@abp/aspnetcore.mvc.ui.theme.shared@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.1.0-rc.2.tgz#e5056e4e159f5815e3cffecab5c46f3d7d4f79d7" + integrity sha512-bo56XzQZPYL/3ckWTTTSSUsSFSFJobvfE29cz13NIrZ/tBtWyQCAJn92wYHuY+6IezYUWb4ga3PkFeHRzR142A== + dependencies: + "@abp/aspnetcore.mvc.ui" "~10.1.0-rc.2" + "@abp/bootstrap" "~10.1.0-rc.2" + "@abp/bootstrap-datepicker" "~10.1.0-rc.2" + "@abp/bootstrap-daterangepicker" "~10.1.0-rc.2" + "@abp/datatables.net-bs5" "~10.1.0-rc.2" + "@abp/font-awesome" "~10.1.0-rc.2" + "@abp/jquery-form" "~10.1.0-rc.2" + "@abp/jquery-validation-unobtrusive" "~10.1.0-rc.2" + "@abp/lodash" "~10.1.0-rc.2" + "@abp/luxon" "~10.1.0-rc.2" + "@abp/malihu-custom-scrollbar-plugin" "~10.1.0-rc.2" + "@abp/moment" "~10.1.0-rc.2" + "@abp/select2" "~10.1.0-rc.2" + "@abp/sweetalert2" "~10.1.0-rc.2" + "@abp/timeago" "~10.1.0-rc.2" + +"@abp/aspnetcore.mvc.ui@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.1.0-rc.2.tgz#e25d3575d40bfcb3f809bd2d355671181ee5ff40" + integrity sha512-MOF86bVbi7N/nIla+361nsBrN4tiSka8xzpWcgqlLcCAl9ILG4rugbtafBAjN81taPma2peZM7egaOR4SDkTMw== dependencies: ansi-colors "^4.1.3" -"@abp/bootstrap-datepicker@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.1.0-rc.1.tgz#54730dc001dab746af18c41d51b73949742e3b2d" - integrity sha512-DhVfA9WjfRQrUwjN2eJTCjLEhAU7rLjWgMlifW1id3HbdMs9emKn0rzQqhZ8MOSUNBwLkbUWcXWnojRUFgUB+w== +"@abp/bootstrap-datepicker@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.1.0-rc.2.tgz#be80c6104ba53e18935fbf62ca2c1890f4b2fde4" + integrity sha512-BNcDYUSbZaLah4SfXm0efoqFTsOViVm6370k9L7vix/OGpIWwklJsr8y78lvdM5ANgNCfl0LPSq+seLJFc/OLA== dependencies: bootstrap-datepicker "^1.10.1" -"@abp/bootstrap-daterangepicker@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.1.0-rc.1.tgz#9af7ffe1d1bd4fc99e1606e30189e6f28bb2f201" - integrity sha512-/Hsge8UwLZFuPNWRZhpf4nVB4urPYsaKAuCz0pSH5OHmS4FWr0Ng3vnUd9fcSeaq9OV2HarcgGWDapYtszttVQ== +"@abp/bootstrap-daterangepicker@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.1.0-rc.2.tgz#f189f7d070ebd97d9cfdcb99571cab2d6a198ab5" + integrity sha512-bV8J0MuiAFVLkr48JsB6aZU6aPoqw+Gyhq1szQ74bEwNQlRBPuF92WVA5FACaUBj8dMUzR9HDDAYQuxUzpKYKA== dependencies: bootstrap-daterangepicker "^3.1.0" -"@abp/bootstrap@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.1.0-rc.1.tgz#e937f0a10874c0f3b0f719ff6a316b0ca91e65c5" - integrity sha512-GlRyH5oCjcmFk1eBGCHk6GglL2DJ5RLlDXn7HWU675u48DCQTkBbKP9qA3RcYERQB4qqhcOKEDtYPZVljRVHRQ== +"@abp/bootstrap@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.1.0-rc.2.tgz#2300800a29ea09b91f5ed2e6177e5921fe7d2a0f" + integrity sha512-K+tDI9vz/Y9B/yu0i3AVpm4v3Odi44Q/yH5hAprL7f4pGxEOiqAFB/qzHAxG+7Oa7wjv5tPLv+Cz4DavBQjd8Q== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" bootstrap "^5.3.8" -"@abp/clipboard@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/clipboard/-/clipboard-10.1.0-rc.1.tgz#ebb677e689372d5c0d07a5814b61eae45397a3c8" - integrity sha512-iJpKYQJy5mN1O8n0+4DsWoWKAOasLuUE9Cja01Yl7YTb6XwYCGozBAVZbi+YouhYmsSr2zM4ozKxXPbXXt0u1w== +"@abp/clipboard@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/clipboard/-/clipboard-10.1.0-rc.2.tgz#e99dbf190e3684e99c8e909bf38201c70e267502" + integrity sha512-kRS9pWc1jRgr4D4/EV9zdAy3rhhGBrcqk2as5+6Ih49npsEJY/cF5mYH7mj/ZYy8SHqtae/CR7bZsR+uCDKYrQ== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" clipboard "^2.0.11" -"@abp/cms-kit.admin@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/cms-kit.admin/-/cms-kit.admin-10.1.0-rc.1.tgz#800ab806e343c0464eb775ef844e77a0e56f07ce" - integrity sha512-RkMTl9eDXqOC/fmun4gsm+glWUCmGJUtv6DnLu8m3wBuwLGyac64o9shpie/6TFvF99agtUp+4se5c8PO4m0tA== +"@abp/cms-kit.admin@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/cms-kit.admin/-/cms-kit.admin-10.1.0-rc.2.tgz#554d15856007e80cc2994d57612d08e3d6747e3e" + integrity sha512-mi9m4Nr51wyq/EN8DRMT/QlSqgIwD7WKDDSO80Tylr46r5A7wiGbgNIyy+hnHCR9IbORpTRv8VjMTtkjF7CRPA== dependencies: - "@abp/codemirror" "~10.1.0-rc.1" - "@abp/jstree" "~10.1.0-rc.1" - "@abp/markdown-it" "~10.1.0-rc.1" - "@abp/slugify" "~10.1.0-rc.1" - "@abp/tui-editor" "~10.1.0-rc.1" - "@abp/uppy" "~10.1.0-rc.1" + "@abp/codemirror" "~10.1.0-rc.2" + "@abp/jstree" "~10.1.0-rc.2" + "@abp/markdown-it" "~10.1.0-rc.2" + "@abp/slugify" "~10.1.0-rc.2" + "@abp/tui-editor" "~10.1.0-rc.2" + "@abp/uppy" "~10.1.0-rc.2" -"@abp/cms-kit.public@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/cms-kit.public/-/cms-kit.public-10.1.0-rc.1.tgz#1f38e4f848da7898d37e66263e807a7cefe8df95" - integrity sha512-owy45DYVAxnu0UYhpqPctIfbM7m/NxYREjnhq+41BaXXiTDLOXEsSoucwObDevnvwthYDuB+4CXK3NYz7x/8+g== +"@abp/cms-kit.public@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/cms-kit.public/-/cms-kit.public-10.1.0-rc.2.tgz#1e25575fa7dde2661970942c868250f749ffcf7e" + integrity sha512-BbEL17D0k44+P1pBfmrRwhwukXqbggprl8DWBofBkPut1HUmo1iaD4NrMEG5NZC03L8LRDVNm3hx2zI1voF9Sg== dependencies: - "@abp/highlight.js" "~10.1.0-rc.1" - "@abp/star-rating-svg" "~10.1.0-rc.1" + "@abp/highlight.js" "~10.1.0-rc.2" + "@abp/star-rating-svg" "~10.1.0-rc.2" -"@abp/cms-kit@10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/cms-kit/-/cms-kit-10.1.0-rc.1.tgz#c359a8b392644d17cea1a000169a728dc8ea42f4" - integrity sha512-YSnmmygVa7NZShoI/brrBIiM2SLdSLwCEmxC7Igml0j0BfO7MYBQ0BlY2dcdun9jtwv52V9OZQvtmnuObSolkw== +"@abp/cms-kit@10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/cms-kit/-/cms-kit-10.1.0-rc.2.tgz#18d0818a07a530fccb23fa082db24e439854e94a" + integrity sha512-cwraU613er9EybLoU9wXiqfFFBjSyk/DnbZkAviavOLg+QRTFdDoJEVTPpNn6WCXc1oS5/LPuu/744MrGixYyw== dependencies: - "@abp/cms-kit.admin" "~10.1.0-rc.1" - "@abp/cms-kit.public" "~10.1.0-rc.1" + "@abp/cms-kit.admin" "~10.1.0-rc.2" + "@abp/cms-kit.public" "~10.1.0-rc.2" -"@abp/codemirror@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/codemirror/-/codemirror-10.1.0-rc.1.tgz#6a1a6d7ed56a33385f7fbc48cbf49697dde485f8" - integrity sha512-XoVp2Jh1xwkLL8ZPuWo3kxrr/bgU56WwG3fegNCfJY1RsFckNGI8VKvka/+AQaCxfq44hYWurm/3c2JjkI392A== +"@abp/codemirror@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/codemirror/-/codemirror-10.1.0-rc.2.tgz#8a55a66c8be83995045bc2724ca02c29771283a3" + integrity sha512-8jlMySyFTNnDJvQLqZgj7S0EZ0oGWoplDFDE6zEP7NBK8INzrw7D7lc93XZ8/G7lRh0xeM1OcziUHZEX/QhXNg== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" codemirror "^5.65.1" -"@abp/core@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.1.0-rc.1.tgz#d9ce0958407fa39c8015eb2d9328a3a157aaa4f0" - integrity sha512-pFsaQei1W0JRJkhNdlxq+EDP7x+83nT0UHZetTHNzp8tkP4Cn8Ni/ZODIUku8ck1vcKc0X3pXKj42BN+yZ1g3g== +"@abp/core@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.1.0-rc.2.tgz#403687aff5a30788f7b7ca660abdfd85d89438aa" + integrity sha512-euuG2Hna/DT6/R1dGOjgp3vcehYtF+CcOkRj31oquYKaM5YWk4OaZ314DSpnjgs/xo8DuVc4eKFQwIxD9RK41w== dependencies: - "@abp/utils" "~10.1.0-rc.1" + "@abp/utils" "~10.1.0-rc.2" -"@abp/datatables.net-bs5@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.1.0-rc.1.tgz#7981bc64c543d24aa5b0ab373f5693187990d5be" - integrity sha512-yaYHuZ9eVHT2cZcH9r680X+imWhOqbhWlOlbLDpJtg7Un0ejDzou+JqxKYHHXmSEKYEPHjfCEhnXkMnyqvSUHQ== +"@abp/datatables.net-bs5@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.1.0-rc.2.tgz#a60650d1802b40751d30f8f6c56beb23fd66481b" + integrity sha512-IWwexNqbMpET54Fvm9LoPTJYf+4CoBbjFOvz3sL6CgO2feV5R5fKigjVU8zXKNh2W+RG8L6zEarfVxrr114TsA== dependencies: - "@abp/datatables.net" "~10.1.0-rc.1" + "@abp/datatables.net" "~10.1.0-rc.2" datatables.net-bs5 "^2.3.4" -"@abp/datatables.net@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.1.0-rc.1.tgz#f487357690060dd055dbb7f54e1298e8d8a81e40" - integrity sha512-KTKTW73H9vaX6J9ipXUf7d3QdseKaFRzl7e1QKldOoQxBs4pzT2NATGdUyiAnJsfPl4scpSFetzI66Rs7r3idg== +"@abp/datatables.net@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.1.0-rc.2.tgz#9147f68bc6dbc4eb40a9ddf65c7859e788cdcac2" + integrity sha512-a9DJpwg14S4nVOiC4ipw0CQwEYWB602e2gCJiH7W1mxopbQb135RxwhtdTnW//eIONcxC9IrEuvcBEAUVt2B7Q== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" datatables.net "^2.3.4" -"@abp/font-awesome@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.1.0-rc.1.tgz#2ee67b07ce6296c7f0ac5217c9d9677b9c70f05e" - integrity sha512-O/3oLbSu1pMNftQzQW29gSpkWCFJazFKjT7sOYId1Ueric0svyJ42hFr5gHf9RFVmCU5pgyhawcoTyzQ34PxYQ== +"@abp/font-awesome@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.1.0-rc.2.tgz#364466cfe67e41e0c4d16b57d3923d10f66369f1" + integrity sha512-F1Jy8xoFV2aA+VN+NH1gtrG96/j9w7Picc+KLoCoIyNnJr/xJur11XkJyu5ln8KF4V7p/DY7QaQodWV/btOs8g== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" "@fortawesome/fontawesome-free" "^7.0.1" -"@abp/highlight.js@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/highlight.js/-/highlight.js-10.1.0-rc.1.tgz#52667c1b0242b710d1b8ea65cf9c8dce50c2a213" - integrity sha512-LlcVW/R+WPy8DqWmCfkbxwsWVHdo4jqn1/wvcSuxtPYsQZcBLocM+IgmT1Be3Ti1gn5Gu2h89UsdYlDfLMllqQ== +"@abp/highlight.js@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/highlight.js/-/highlight.js-10.1.0-rc.2.tgz#6ad0e1ef9e49f0f90eba9d899fd72b6c30a4f9f0" + integrity sha512-jAX4p+i3secAaI3waXoMr7yoH6G1nWvcjR5UVin168H7I4UySxbF799T89v5tK8gtfWgaTjEydFZRypSQU/dHg== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" "@highlightjs/cdn-assets" "~11.11.1" -"@abp/jquery-form@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-10.1.0-rc.1.tgz#60fc98085671a2ff08e8c411847354b6a48cf55e" - integrity sha512-bj70hE7MsH2AoBb2yU2d6ng5Bo/wqjCNUI0kbG0T1T4yp1cToGAVJ6hrd+KOp2Tf6W+NITn1EycxRTCktE9QRA== +"@abp/jquery-form@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-10.1.0-rc.2.tgz#3857717d07569c22d4bbbe459238abeb816d606a" + integrity sha512-2D5WHVnfK9bhRces1tgPwOEoc7KCYKYiKHBOcqct+LTA7zoRjJv/PM8/JhFVl+grVIw1aSwO4tU3YfZ22Vxipg== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" jquery-form "^4.3.0" -"@abp/jquery-validation-unobtrusive@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.1.0-rc.1.tgz#a820989d8daf53096f838817cb2b3dba21f71653" - integrity sha512-7uXxbvuZ+ecD5suH3nx/ggiFAA8GjXGAB5tuGCdqePdBn1PMzDIDI1ZP9q8sR7sMjo7RKVK6MzX+PazUuJ4L7g== +"@abp/jquery-validation-unobtrusive@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.1.0-rc.2.tgz#efd7b69a078a20c0bf405408dbdf52a7bf770b3b" + integrity sha512-tZ0MWgzBqp+SNfMxM0z2cGB21NiTHuVJyyQaXKE/ptuD5pc0uRkcqw/J2kWfiqsoVgChz27IB6h8/jqDafS4qg== dependencies: - "@abp/jquery-validation" "~10.1.0-rc.1" + "@abp/jquery-validation" "~10.1.0-rc.2" jquery-validation-unobtrusive "^4.0.0" -"@abp/jquery-validation@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.1.0-rc.1.tgz#c8630b0a53ee93218be5c82264085b36b0b07936" - integrity sha512-/eS4vy5gwHIaRr6aHkvO6xWRjL22bVly7S0AqbZw2OxJ0pw1n23n+zipn/NDRSngRZSajTLLm1pYCG5qlJGd3A== +"@abp/jquery-validation@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.1.0-rc.2.tgz#d39537a7356c51f9db2e66f6740cf6df86bd0442" + integrity sha512-LOkS0NKk4pLtLjPU0CCbwROyUg6EtJN8Z/it7QuKK1CIRfYYcAStgNnNm5geZP7CqECIkoiFfgWjI+L5Z9/Tfg== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" jquery-validation "^1.21.0" -"@abp/jquery@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.1.0-rc.1.tgz#38d5d5b5a6144bf207773ef26f36d381a257a635" - integrity sha512-YarNlYPG7MfLc0fSnOcVrp9Da1EUxz4zUERnTOQ0/EdOUdcFhGAqI2yfoTFCxqX+TSphQWZEdif/tsfULtkI1A== +"@abp/jquery@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.1.0-rc.2.tgz#101a55f70d510978c8c05f5857d0e9d4965263f7" + integrity sha512-bQV1uFWGtwRYjNOsqJ8FM2004idX2Jj7YVL19YF1/PjyPUSMX+s8/IvJizBjyY5hPAiWBBhmV9g+IFWzxlDQoQ== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" jquery "~3.7.1" -"@abp/jstree@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jstree/-/jstree-10.1.0-rc.1.tgz#1a0ee3ed28a84f959df41b5192199e18f8a8b60c" - integrity sha512-37XpZscnQyAK3oiz27dcAnyqGkcIs38Coxr4D65USULm84f/YGEV9QbKwqYIvdtj5Xac4IXQ5K2jANj+TRIuBg== +"@abp/jstree@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jstree/-/jstree-10.1.0-rc.2.tgz#0b145af388ca527b107464ff706c5f901344a094" + integrity sha512-RmKRKVuQBhemapF4wmNVOZfDxBSjqKoSRL/Y0Sbyqx86Qg/HYpFZD1zQgaH6NFwUOpwB4wVJRBHV5r7i4LxxyQ== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" jstree "^3.3.17" -"@abp/lodash@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.1.0-rc.1.tgz#bf735e34a42ddcdf19e331c8c5cf1a24a87475cb" - integrity sha512-JQV0oFLE90V9GdrD37PTMiOylw/U6yjpDu5PblVW/zlNmw/TzEtukp3/vJl7GJxoKRYWtgJf1nFLgG/+2kjE1A== +"@abp/lodash@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.1.0-rc.2.tgz#d08c03f8d3d0fbaa3e71e603cbe5fb7f176933ef" + integrity sha512-KCnD1p2y52ZI+2ifpiFIUAiDPsKehnOD8HV5qKeObO6UCP97okif8IP+sQDmNQb8O33y/NKTyx/HcpwBbe/NYQ== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" lodash "^4.17.21" -"@abp/luxon@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.1.0-rc.1.tgz#1c883ad3a5653f0c6ce4965e4b4ce21543129534" - integrity sha512-SDtwjIDR4OzrtzRWpgb8atUSK0DwajJ5LC486xHq5ftnbFETRfOwihyIfC2H4oIPPDE5oS+LOnjCkRYVkGt0oQ== +"@abp/luxon@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.1.0-rc.2.tgz#ef8d2b323bac054fc9610e241e1b1763d229e065" + integrity sha512-qYFl6XO3g9mZiu0dtIczI7LRuYWwc+RkpbDzSmruXcRks3KA+ZZco2vhHNnlwtXcINl/TXtbW7Wc0MX+8IB1Kw== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" luxon "^3.7.2" -"@abp/malihu-custom-scrollbar-plugin@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.1.0-rc.1.tgz#770106795f5eba9d36dd2d4002e450c215c66787" - integrity sha512-EFwQ7rUiPM6mxznn1t23ySzQH+VQ0dB2YxOqBAluFR/yC3qypjb98jGQaZKsJx9+iT4DuG9gGPKOfct1Sqh0KQ== +"@abp/malihu-custom-scrollbar-plugin@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.1.0-rc.2.tgz#dfaf666442c7c122f7da72c83b9adf194d5b6ec8" + integrity sha512-PudMHmNQgZ6JZeaVt1ZoXLqO0UZXJzUYiBah2LDkC4EMLjnMJFINHBoEVVa4ooXH0yjFv+zsbN0vWZYJ8TBJIA== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/markdown-it@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/markdown-it/-/markdown-it-10.1.0-rc.1.tgz#277508743134503937f56bc764e6554d7f37f402" - integrity sha512-JBPhX9t6Err9kcZTwFC8ib3QaNGRKs6ucme+h7GY592WUFDzcxOIAoOPguFvn3x1nQCXNOEqy5NFO2BVRfN6qw== +"@abp/markdown-it@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/markdown-it/-/markdown-it-10.1.0-rc.2.tgz#43865464fb44f470baf30296712a5f007ff9730d" + integrity sha512-R45uA/EfMEZSkWchdpE/epOPsjswuQDB+IyBdwil2kKe3Bfv3yIh8+GSU5hfhiArXmRbhcmNGdeTylXWhrInbw== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" markdown-it "^14.1.0" -"@abp/moment@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.1.0-rc.1.tgz#fe5bfa5bc28bb847cf801523c1b2f118ffb12447" - integrity sha512-P6z9YO/FC/5DhTDWsHkquo5tEzIhSfUnRefLcZtv7XuJtevH/93NFSr23RL6D+F4r3cB5GMXGFUYmXGKWoXGHg== +"@abp/moment@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.1.0-rc.2.tgz#610a1592d13984aea51abbd13df8c5995a089149" + integrity sha512-ep8PnAXARw0t/wtGOVp/oiNhF3B0Bh6y2vRzKrcSoyXAQREGGm4fJdZVYZLGTfI4lFLTjebEgf4O7T9feUwJAw== dependencies: moment "^2.30.1" -"@abp/prismjs@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/prismjs/-/prismjs-10.1.0-rc.1.tgz#26ef6c368dbfdd27d2836f1dd8cd9e8cf630f0ab" - integrity sha512-6OpD/w7NOSI7OpjubMx45gXsZY5K6ggynJ9uSqv3NDxSbj9l6c8cRTX1i56ATT10rhyYxZIw83FRhMlxOiLfjg== +"@abp/prismjs@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/prismjs/-/prismjs-10.1.0-rc.2.tgz#8565bab503a16fc349f4b0fa2609ad412ff838be" + integrity sha512-SmZWMyJ3cJW+qj4CWJ7y2kD6PMx2zfZMA5X5jPunsytG4Eht4AVyIR38Y4QSpO62zZgkHyZlSTFOozBfhrlv9A== dependencies: - "@abp/clipboard" "~10.1.0-rc.1" - "@abp/core" "~10.1.0-rc.1" + "@abp/clipboard" "~10.1.0-rc.2" + "@abp/core" "~10.1.0-rc.2" prismjs "^1.30.0" -"@abp/select2@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.1.0-rc.1.tgz#19432bb6483f8a3470f02a3037612835b4c54ef4" - integrity sha512-xPvdrgXZzRhEIrEyw9eoITc+hYlbi8fSW3BGQ1ZMBXWgYkjWxhj/1AuLsu2ewFAKdBNz+PewjA0MKP6iBLATHw== +"@abp/select2@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.1.0-rc.2.tgz#40c5418d007fc36817eecbe6388d767e4e7ca887" + integrity sha512-Pq0wlpL01sWRLUg5um3JtBXIqi3mmbwPwvgxP8hFbQngAt9JXAK8geNRiTMrIZgtW/ycXtM1v6I4zuWOLOeAGg== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" select2 "^4.0.13" -"@abp/slugify@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/slugify/-/slugify-10.1.0-rc.1.tgz#86aa65da4e80d1b651fb029e9366e84ceefde201" - integrity sha512-MlIJ+7ee6IT1pH+5IUe4w+jfdZZIn1NAOSE0vmM8iz82MIgYcPp8PdV1qBeO+v8BlgfGJebKCul0VX98debbxw== +"@abp/slugify@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/slugify/-/slugify-10.1.0-rc.2.tgz#42389b8471f4aacd340bebe664994b778eb335ab" + integrity sha512-jhLEqxPpitTfwJgHW21urQnvP3NU+/hXZXzmnT6ZPBw7pVpbN2m1hDOm+JC+zKkLaa3WQ23rE9ewwrnJaoQ67w== dependencies: slugify "^1.6.6" -"@abp/star-rating-svg@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/star-rating-svg/-/star-rating-svg-10.1.0-rc.1.tgz#48b87590495258264d02443a577ee82f0ac4c15b" - integrity sha512-DLr6NpGfhmF94uaXYdMFDpusMZ4SVHEOu2z4tJ+h8LelaOBvQSKBMjx2yc2R7fByReiujRc1JZeIhZKboZfVSA== +"@abp/star-rating-svg@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/star-rating-svg/-/star-rating-svg-10.1.0-rc.2.tgz#d1754352cc3af1f42577de19e18350b289685e68" + integrity sha512-3Io7LMSnjQtc1b5CP1nZ8ucvXfq262PGMTHhwgM30xnvO9HJbtnppCmwVTbhZBQigKQFnySUKmccQbGjoqnN4A== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" star-rating-svg "^3.5.0" -"@abp/sweetalert2@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.1.0-rc.1.tgz#40f4d6964cd6620dcb81040d84e432f4ae314686" - integrity sha512-E4dUC2k/kClBnadtGK5NWR9YmCGgKq5uOGjEVtzXxJH62MlaCXlcxRrK9wNzX8pzDMDYFKjuOUugoROEgbRKWQ== +"@abp/sweetalert2@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.1.0-rc.2.tgz#d35858c69e10c6726b02cdfcea88dfc32385963f" + integrity sha512-s9VPRToohN45uzHcKCF5Mcj8FVjsXcXUb0U3tuaT/Y+u4adHB3fBxYiXJFM0sVsCJ81dFktxwka40Wm8Taz/zA== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" sweetalert2 "^11.23.0" -"@abp/timeago@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.1.0-rc.1.tgz#482e3a645f63ee9adf708ef8ae895fcf2bcb4271" - integrity sha512-UOb2o6bvi3+2kbD7+rUJdqTQvTbgqUD7ktd0U5Hulb3dBOyv6MZEagzMbXA003OROiS23JzzT7M0lLStvixwlg== +"@abp/timeago@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.1.0-rc.2.tgz#98d630cc3843eee64dbcc34fb8ca5afbab034718" + integrity sha512-vJmk+otyXXJE2s2J8iYpLVaFuNAYnIUSOitmi7umYnL+k/UE2KQhBXU7FR0/OBY9mAZYd+shaiGIU1LMSaJ+Xg== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" timeago "^1.6.7" -"@abp/tui-editor@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/tui-editor/-/tui-editor-10.1.0-rc.1.tgz#fd9d014ea5bbdc9c93b3160040928cc27ba50d5e" - integrity sha512-X6tlZs3/soUbl/L/aiQfIOZuiZRfmlUpEukIduJBlDsSaNR2hJgEa98RsQNvQ2yvSU/H7+9kwIwdizhQdimujA== +"@abp/tui-editor@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/tui-editor/-/tui-editor-10.1.0-rc.2.tgz#ebbd5bad1ee180a0c6e6a9cfd894499614a71e96" + integrity sha512-k5V+5ZE+HZebfyXLzddRQDGri3HP7wSjDXEbSMLTgxZTem7IzksyLWLAN/woKRzWX92BJXcsmR8T1rhuMhohhA== dependencies: - "@abp/jquery" "~10.1.0-rc.1" - "@abp/prismjs" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" + "@abp/prismjs" "~10.1.0-rc.2" -"@abp/uppy@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/uppy/-/uppy-10.1.0-rc.1.tgz#aa849234c078238c9bf6af8c181a1432ac628d22" - integrity sha512-0utLga9u7Do3BH1Nou7t01a9f89fhe5A6NqCvQg91opKmMFYOke5PiGAUOvbbbBh6kZDlPw9dc9E6fOhIBXh2w== +"@abp/uppy@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/uppy/-/uppy-10.1.0-rc.2.tgz#1b9ca53eda178e8ea5d14573da15b9c141a89fc0" + integrity sha512-QHgTiVaZKZrnNigyE1F7OR3Tdtjdq6tjm164HWAPJSJOUbcv6v498Q7DWS2dIZMU2e/226HflS2NwwNV87s3XA== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" uppy "^5.1.2" -"@abp/utils@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.1.0-rc.1.tgz#e53ef81f39fd620b41ae2bfb69d06ee85ccb28ad" - integrity sha512-xBHIw5rmAttN5PL5C84L2znHkA2HMrP6HaA2aV5k6KU7BtOWJhviZnKJgbUBfufNQNeLwlThCHAfS7aOv/UQkg== +"@abp/utils@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.1.0-rc.2.tgz#86a980c6536b3b5ce185d406723b28be421864ac" + integrity sha512-Oz863VNA8fraQ81vTvqM0IqwiaseLwfFU5QNn6iOGOfn5wQrEkPwtZ0jMI+DGNtJgPzoKiq+iKc3K+SiuVgldg== dependencies: just-compare "^2.3.0" diff --git a/modules/docs/app/VoloDocs.Web/package.json b/modules/docs/app/VoloDocs.Web/package.json index d802372318..0a0a8f4726 100644 --- a/modules/docs/app/VoloDocs.Web/package.json +++ b/modules/docs/app/VoloDocs.Web/package.json @@ -3,7 +3,7 @@ "name": "volo.docstestapp", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~10.1.0-rc.1", - "@abp/docs": "~10.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.basic": "~10.1.0-rc.2", + "@abp/docs": "~10.1.0-rc.2" } } diff --git a/modules/docs/app/VoloDocs.Web/yarn.lock b/modules/docs/app/VoloDocs.Web/yarn.lock index 89b737d361..e99b91ff81 100644 --- a/modules/docs/app/VoloDocs.Web/yarn.lock +++ b/modules/docs/app/VoloDocs.Web/yarn.lock @@ -2,229 +2,229 @@ # yarn lockfile v1 -"@abp/anchor-js@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/anchor-js/-/anchor-js-10.1.0-rc.1.tgz#976f553f72e8fe6d9dca3886906c12a7d980f8f0" - integrity sha512-pbgMsNGPhS8IKvSAzrF3d+8KxKqEYi1pssnfMvw+5LvfW2gdRnofjYuTFTaFzCQnYzUFlhVKSHd9yjz7RrWrvw== +"@abp/anchor-js@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/anchor-js/-/anchor-js-10.1.0-rc.2.tgz#5ed35c420ec53f9e305e9de943ebb005677d7617" + integrity sha512-D8rH9p8gGbNc6FpW24CFFW9JZD7tUiJkM3Lx31RsygwvmlolutN4jpzV5L9h9IvbrxJCT2jSqK1s686z8SHb3w== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" anchor-js "^5.0.0" -"@abp/aspnetcore.mvc.ui.theme.basic@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-10.1.0-rc.1.tgz#99407c4108b685eef91f21d038cc5bd2343b5847" - integrity sha512-UbVgYf1K+zUqueNCwOl4QoUAz7nwJyoQJSqq0gi7LA9JBRBbb8nDA8szQjAUVqF4h+k4o61588MUQnerUQlZ6g== - dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~10.1.0-rc.1" - -"@abp/aspnetcore.mvc.ui.theme.shared@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.1.0-rc.1.tgz#7c0f721bdd4ec99f441a49e662a457d98c2c66fa" - integrity sha512-dB6wmR3C43Vn1/hiWY3IkxivUEWTGhO4mj3hffwAeyJqnEUh6kQUW6UfmYNGEhahSjhVIS3lUZ4x+R+yGCpemw== - dependencies: - "@abp/aspnetcore.mvc.ui" "~10.1.0-rc.1" - "@abp/bootstrap" "~10.1.0-rc.1" - "@abp/bootstrap-datepicker" "~10.1.0-rc.1" - "@abp/bootstrap-daterangepicker" "~10.1.0-rc.1" - "@abp/datatables.net-bs5" "~10.1.0-rc.1" - "@abp/font-awesome" "~10.1.0-rc.1" - "@abp/jquery-form" "~10.1.0-rc.1" - "@abp/jquery-validation-unobtrusive" "~10.1.0-rc.1" - "@abp/lodash" "~10.1.0-rc.1" - "@abp/luxon" "~10.1.0-rc.1" - "@abp/malihu-custom-scrollbar-plugin" "~10.1.0-rc.1" - "@abp/moment" "~10.1.0-rc.1" - "@abp/select2" "~10.1.0-rc.1" - "@abp/sweetalert2" "~10.1.0-rc.1" - "@abp/timeago" "~10.1.0-rc.1" - -"@abp/aspnetcore.mvc.ui@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.1.0-rc.1.tgz#290297353fdc15826a870b35162a0bbf4c9bc948" - integrity sha512-RBR5wPf5ygzuSpfXG5MeOGd/YOtQH5pVyU65T4DMtGpLKHQxgxbh7JK1btyC5zGmzr/Ds5+5Mcd7S66dIckDJQ== +"@abp/aspnetcore.mvc.ui.theme.basic@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-10.1.0-rc.2.tgz#599f5c47a417d1230fc17c0446a0229f920f7246" + integrity sha512-8F4nEK+VtgRRf8n+66HMbtCEaOMCW/OdbSEWRl9ahMNoj860oPIJ8P8Qn/2+LjtkPMdDAfCdEzyDzCd3igaFaA== + dependencies: + "@abp/aspnetcore.mvc.ui.theme.shared" "~10.1.0-rc.2" + +"@abp/aspnetcore.mvc.ui.theme.shared@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.1.0-rc.2.tgz#e5056e4e159f5815e3cffecab5c46f3d7d4f79d7" + integrity sha512-bo56XzQZPYL/3ckWTTTSSUsSFSFJobvfE29cz13NIrZ/tBtWyQCAJn92wYHuY+6IezYUWb4ga3PkFeHRzR142A== + dependencies: + "@abp/aspnetcore.mvc.ui" "~10.1.0-rc.2" + "@abp/bootstrap" "~10.1.0-rc.2" + "@abp/bootstrap-datepicker" "~10.1.0-rc.2" + "@abp/bootstrap-daterangepicker" "~10.1.0-rc.2" + "@abp/datatables.net-bs5" "~10.1.0-rc.2" + "@abp/font-awesome" "~10.1.0-rc.2" + "@abp/jquery-form" "~10.1.0-rc.2" + "@abp/jquery-validation-unobtrusive" "~10.1.0-rc.2" + "@abp/lodash" "~10.1.0-rc.2" + "@abp/luxon" "~10.1.0-rc.2" + "@abp/malihu-custom-scrollbar-plugin" "~10.1.0-rc.2" + "@abp/moment" "~10.1.0-rc.2" + "@abp/select2" "~10.1.0-rc.2" + "@abp/sweetalert2" "~10.1.0-rc.2" + "@abp/timeago" "~10.1.0-rc.2" + +"@abp/aspnetcore.mvc.ui@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.1.0-rc.2.tgz#e25d3575d40bfcb3f809bd2d355671181ee5ff40" + integrity sha512-MOF86bVbi7N/nIla+361nsBrN4tiSka8xzpWcgqlLcCAl9ILG4rugbtafBAjN81taPma2peZM7egaOR4SDkTMw== dependencies: ansi-colors "^4.1.3" -"@abp/bootstrap-datepicker@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.1.0-rc.1.tgz#54730dc001dab746af18c41d51b73949742e3b2d" - integrity sha512-DhVfA9WjfRQrUwjN2eJTCjLEhAU7rLjWgMlifW1id3HbdMs9emKn0rzQqhZ8MOSUNBwLkbUWcXWnojRUFgUB+w== +"@abp/bootstrap-datepicker@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.1.0-rc.2.tgz#be80c6104ba53e18935fbf62ca2c1890f4b2fde4" + integrity sha512-BNcDYUSbZaLah4SfXm0efoqFTsOViVm6370k9L7vix/OGpIWwklJsr8y78lvdM5ANgNCfl0LPSq+seLJFc/OLA== dependencies: bootstrap-datepicker "^1.10.1" -"@abp/bootstrap-daterangepicker@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.1.0-rc.1.tgz#9af7ffe1d1bd4fc99e1606e30189e6f28bb2f201" - integrity sha512-/Hsge8UwLZFuPNWRZhpf4nVB4urPYsaKAuCz0pSH5OHmS4FWr0Ng3vnUd9fcSeaq9OV2HarcgGWDapYtszttVQ== +"@abp/bootstrap-daterangepicker@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.1.0-rc.2.tgz#f189f7d070ebd97d9cfdcb99571cab2d6a198ab5" + integrity sha512-bV8J0MuiAFVLkr48JsB6aZU6aPoqw+Gyhq1szQ74bEwNQlRBPuF92WVA5FACaUBj8dMUzR9HDDAYQuxUzpKYKA== dependencies: bootstrap-daterangepicker "^3.1.0" -"@abp/bootstrap@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.1.0-rc.1.tgz#e937f0a10874c0f3b0f719ff6a316b0ca91e65c5" - integrity sha512-GlRyH5oCjcmFk1eBGCHk6GglL2DJ5RLlDXn7HWU675u48DCQTkBbKP9qA3RcYERQB4qqhcOKEDtYPZVljRVHRQ== +"@abp/bootstrap@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.1.0-rc.2.tgz#2300800a29ea09b91f5ed2e6177e5921fe7d2a0f" + integrity sha512-K+tDI9vz/Y9B/yu0i3AVpm4v3Odi44Q/yH5hAprL7f4pGxEOiqAFB/qzHAxG+7Oa7wjv5tPLv+Cz4DavBQjd8Q== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" bootstrap "^5.3.8" -"@abp/clipboard@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/clipboard/-/clipboard-10.1.0-rc.1.tgz#ebb677e689372d5c0d07a5814b61eae45397a3c8" - integrity sha512-iJpKYQJy5mN1O8n0+4DsWoWKAOasLuUE9Cja01Yl7YTb6XwYCGozBAVZbi+YouhYmsSr2zM4ozKxXPbXXt0u1w== +"@abp/clipboard@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/clipboard/-/clipboard-10.1.0-rc.2.tgz#e99dbf190e3684e99c8e909bf38201c70e267502" + integrity sha512-kRS9pWc1jRgr4D4/EV9zdAy3rhhGBrcqk2as5+6Ih49npsEJY/cF5mYH7mj/ZYy8SHqtae/CR7bZsR+uCDKYrQ== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" clipboard "^2.0.11" -"@abp/core@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.1.0-rc.1.tgz#d9ce0958407fa39c8015eb2d9328a3a157aaa4f0" - integrity sha512-pFsaQei1W0JRJkhNdlxq+EDP7x+83nT0UHZetTHNzp8tkP4Cn8Ni/ZODIUku8ck1vcKc0X3pXKj42BN+yZ1g3g== +"@abp/core@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.1.0-rc.2.tgz#403687aff5a30788f7b7ca660abdfd85d89438aa" + integrity sha512-euuG2Hna/DT6/R1dGOjgp3vcehYtF+CcOkRj31oquYKaM5YWk4OaZ314DSpnjgs/xo8DuVc4eKFQwIxD9RK41w== dependencies: - "@abp/utils" "~10.1.0-rc.1" + "@abp/utils" "~10.1.0-rc.2" -"@abp/datatables.net-bs5@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.1.0-rc.1.tgz#7981bc64c543d24aa5b0ab373f5693187990d5be" - integrity sha512-yaYHuZ9eVHT2cZcH9r680X+imWhOqbhWlOlbLDpJtg7Un0ejDzou+JqxKYHHXmSEKYEPHjfCEhnXkMnyqvSUHQ== +"@abp/datatables.net-bs5@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.1.0-rc.2.tgz#a60650d1802b40751d30f8f6c56beb23fd66481b" + integrity sha512-IWwexNqbMpET54Fvm9LoPTJYf+4CoBbjFOvz3sL6CgO2feV5R5fKigjVU8zXKNh2W+RG8L6zEarfVxrr114TsA== dependencies: - "@abp/datatables.net" "~10.1.0-rc.1" + "@abp/datatables.net" "~10.1.0-rc.2" datatables.net-bs5 "^2.3.4" -"@abp/datatables.net@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.1.0-rc.1.tgz#f487357690060dd055dbb7f54e1298e8d8a81e40" - integrity sha512-KTKTW73H9vaX6J9ipXUf7d3QdseKaFRzl7e1QKldOoQxBs4pzT2NATGdUyiAnJsfPl4scpSFetzI66Rs7r3idg== +"@abp/datatables.net@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.1.0-rc.2.tgz#9147f68bc6dbc4eb40a9ddf65c7859e788cdcac2" + integrity sha512-a9DJpwg14S4nVOiC4ipw0CQwEYWB602e2gCJiH7W1mxopbQb135RxwhtdTnW//eIONcxC9IrEuvcBEAUVt2B7Q== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" datatables.net "^2.3.4" -"@abp/docs@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/docs/-/docs-10.1.0-rc.1.tgz#b06d4a878de9d5149fcd96e8efa20be02179c2e9" - integrity sha512-+8wV8y1+VO55BHJHZsxW0/OTp7TpauCf/h/QkWjeCdGJ/+sE73FOkZ31Xr+jNCstteF/lwNbK8zFSNQVtQH87g== +"@abp/docs@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/docs/-/docs-10.1.0-rc.2.tgz#ac8c84cdc91c3ac6511ff39f9d8729f0673a1d30" + integrity sha512-oTJX12kZb8CIXQRdjsZx8VYSOgKTA6ei+5CPU4dPVZZiaYBzQ3MNsMlRrrRywwuWCTJedtThy9XOcUwB7LTqCg== dependencies: - "@abp/anchor-js" "~10.1.0-rc.1" - "@abp/clipboard" "~10.1.0-rc.1" - "@abp/malihu-custom-scrollbar-plugin" "~10.1.0-rc.1" - "@abp/popper.js" "~10.1.0-rc.1" - "@abp/prismjs" "~10.1.0-rc.1" + "@abp/anchor-js" "~10.1.0-rc.2" + "@abp/clipboard" "~10.1.0-rc.2" + "@abp/malihu-custom-scrollbar-plugin" "~10.1.0-rc.2" + "@abp/popper.js" "~10.1.0-rc.2" + "@abp/prismjs" "~10.1.0-rc.2" -"@abp/font-awesome@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.1.0-rc.1.tgz#2ee67b07ce6296c7f0ac5217c9d9677b9c70f05e" - integrity sha512-O/3oLbSu1pMNftQzQW29gSpkWCFJazFKjT7sOYId1Ueric0svyJ42hFr5gHf9RFVmCU5pgyhawcoTyzQ34PxYQ== +"@abp/font-awesome@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.1.0-rc.2.tgz#364466cfe67e41e0c4d16b57d3923d10f66369f1" + integrity sha512-F1Jy8xoFV2aA+VN+NH1gtrG96/j9w7Picc+KLoCoIyNnJr/xJur11XkJyu5ln8KF4V7p/DY7QaQodWV/btOs8g== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" "@fortawesome/fontawesome-free" "^7.0.1" -"@abp/jquery-form@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-10.1.0-rc.1.tgz#60fc98085671a2ff08e8c411847354b6a48cf55e" - integrity sha512-bj70hE7MsH2AoBb2yU2d6ng5Bo/wqjCNUI0kbG0T1T4yp1cToGAVJ6hrd+KOp2Tf6W+NITn1EycxRTCktE9QRA== +"@abp/jquery-form@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-10.1.0-rc.2.tgz#3857717d07569c22d4bbbe459238abeb816d606a" + integrity sha512-2D5WHVnfK9bhRces1tgPwOEoc7KCYKYiKHBOcqct+LTA7zoRjJv/PM8/JhFVl+grVIw1aSwO4tU3YfZ22Vxipg== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" jquery-form "^4.3.0" -"@abp/jquery-validation-unobtrusive@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.1.0-rc.1.tgz#a820989d8daf53096f838817cb2b3dba21f71653" - integrity sha512-7uXxbvuZ+ecD5suH3nx/ggiFAA8GjXGAB5tuGCdqePdBn1PMzDIDI1ZP9q8sR7sMjo7RKVK6MzX+PazUuJ4L7g== +"@abp/jquery-validation-unobtrusive@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.1.0-rc.2.tgz#efd7b69a078a20c0bf405408dbdf52a7bf770b3b" + integrity sha512-tZ0MWgzBqp+SNfMxM0z2cGB21NiTHuVJyyQaXKE/ptuD5pc0uRkcqw/J2kWfiqsoVgChz27IB6h8/jqDafS4qg== dependencies: - "@abp/jquery-validation" "~10.1.0-rc.1" + "@abp/jquery-validation" "~10.1.0-rc.2" jquery-validation-unobtrusive "^4.0.0" -"@abp/jquery-validation@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.1.0-rc.1.tgz#c8630b0a53ee93218be5c82264085b36b0b07936" - integrity sha512-/eS4vy5gwHIaRr6aHkvO6xWRjL22bVly7S0AqbZw2OxJ0pw1n23n+zipn/NDRSngRZSajTLLm1pYCG5qlJGd3A== +"@abp/jquery-validation@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.1.0-rc.2.tgz#d39537a7356c51f9db2e66f6740cf6df86bd0442" + integrity sha512-LOkS0NKk4pLtLjPU0CCbwROyUg6EtJN8Z/it7QuKK1CIRfYYcAStgNnNm5geZP7CqECIkoiFfgWjI+L5Z9/Tfg== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" jquery-validation "^1.21.0" -"@abp/jquery@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.1.0-rc.1.tgz#38d5d5b5a6144bf207773ef26f36d381a257a635" - integrity sha512-YarNlYPG7MfLc0fSnOcVrp9Da1EUxz4zUERnTOQ0/EdOUdcFhGAqI2yfoTFCxqX+TSphQWZEdif/tsfULtkI1A== +"@abp/jquery@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.1.0-rc.2.tgz#101a55f70d510978c8c05f5857d0e9d4965263f7" + integrity sha512-bQV1uFWGtwRYjNOsqJ8FM2004idX2Jj7YVL19YF1/PjyPUSMX+s8/IvJizBjyY5hPAiWBBhmV9g+IFWzxlDQoQ== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" jquery "~3.7.1" -"@abp/lodash@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.1.0-rc.1.tgz#bf735e34a42ddcdf19e331c8c5cf1a24a87475cb" - integrity sha512-JQV0oFLE90V9GdrD37PTMiOylw/U6yjpDu5PblVW/zlNmw/TzEtukp3/vJl7GJxoKRYWtgJf1nFLgG/+2kjE1A== +"@abp/lodash@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.1.0-rc.2.tgz#d08c03f8d3d0fbaa3e71e603cbe5fb7f176933ef" + integrity sha512-KCnD1p2y52ZI+2ifpiFIUAiDPsKehnOD8HV5qKeObO6UCP97okif8IP+sQDmNQb8O33y/NKTyx/HcpwBbe/NYQ== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" lodash "^4.17.21" -"@abp/luxon@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.1.0-rc.1.tgz#1c883ad3a5653f0c6ce4965e4b4ce21543129534" - integrity sha512-SDtwjIDR4OzrtzRWpgb8atUSK0DwajJ5LC486xHq5ftnbFETRfOwihyIfC2H4oIPPDE5oS+LOnjCkRYVkGt0oQ== +"@abp/luxon@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.1.0-rc.2.tgz#ef8d2b323bac054fc9610e241e1b1763d229e065" + integrity sha512-qYFl6XO3g9mZiu0dtIczI7LRuYWwc+RkpbDzSmruXcRks3KA+ZZco2vhHNnlwtXcINl/TXtbW7Wc0MX+8IB1Kw== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" luxon "^3.7.2" -"@abp/malihu-custom-scrollbar-plugin@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.1.0-rc.1.tgz#770106795f5eba9d36dd2d4002e450c215c66787" - integrity sha512-EFwQ7rUiPM6mxznn1t23ySzQH+VQ0dB2YxOqBAluFR/yC3qypjb98jGQaZKsJx9+iT4DuG9gGPKOfct1Sqh0KQ== +"@abp/malihu-custom-scrollbar-plugin@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.1.0-rc.2.tgz#dfaf666442c7c122f7da72c83b9adf194d5b6ec8" + integrity sha512-PudMHmNQgZ6JZeaVt1ZoXLqO0UZXJzUYiBah2LDkC4EMLjnMJFINHBoEVVa4ooXH0yjFv+zsbN0vWZYJ8TBJIA== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/moment@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.1.0-rc.1.tgz#fe5bfa5bc28bb847cf801523c1b2f118ffb12447" - integrity sha512-P6z9YO/FC/5DhTDWsHkquo5tEzIhSfUnRefLcZtv7XuJtevH/93NFSr23RL6D+F4r3cB5GMXGFUYmXGKWoXGHg== +"@abp/moment@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.1.0-rc.2.tgz#610a1592d13984aea51abbd13df8c5995a089149" + integrity sha512-ep8PnAXARw0t/wtGOVp/oiNhF3B0Bh6y2vRzKrcSoyXAQREGGm4fJdZVYZLGTfI4lFLTjebEgf4O7T9feUwJAw== dependencies: moment "^2.30.1" -"@abp/popper.js@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/popper.js/-/popper.js-10.1.0-rc.1.tgz#db1fcbd11df41a328456c2a6ae38283f9683611e" - integrity sha512-CJOrNB2T2fC3IiKw46eRr/NDzCTlNwsH6ooEovp4wy0O+t1kk4U/XnguN8wWxCl/SvW1VOAPOrRm6Ar2OABu+w== +"@abp/popper.js@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/popper.js/-/popper.js-10.1.0-rc.2.tgz#09a23add65422b2a34a70dc074bdd8e0ec57d2a2" + integrity sha512-z+YqO0KBr8Nf376sV031lti/bPr2SzuxWgDlNmxrebrF544hgsXC+BXsWhzXSDznEZucN1sIkeoNWEiuAjxctA== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" "@popperjs/core" "^2.11.8" -"@abp/prismjs@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/prismjs/-/prismjs-10.1.0-rc.1.tgz#26ef6c368dbfdd27d2836f1dd8cd9e8cf630f0ab" - integrity sha512-6OpD/w7NOSI7OpjubMx45gXsZY5K6ggynJ9uSqv3NDxSbj9l6c8cRTX1i56ATT10rhyYxZIw83FRhMlxOiLfjg== +"@abp/prismjs@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/prismjs/-/prismjs-10.1.0-rc.2.tgz#8565bab503a16fc349f4b0fa2609ad412ff838be" + integrity sha512-SmZWMyJ3cJW+qj4CWJ7y2kD6PMx2zfZMA5X5jPunsytG4Eht4AVyIR38Y4QSpO62zZgkHyZlSTFOozBfhrlv9A== dependencies: - "@abp/clipboard" "~10.1.0-rc.1" - "@abp/core" "~10.1.0-rc.1" + "@abp/clipboard" "~10.1.0-rc.2" + "@abp/core" "~10.1.0-rc.2" prismjs "^1.30.0" -"@abp/select2@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.1.0-rc.1.tgz#19432bb6483f8a3470f02a3037612835b4c54ef4" - integrity sha512-xPvdrgXZzRhEIrEyw9eoITc+hYlbi8fSW3BGQ1ZMBXWgYkjWxhj/1AuLsu2ewFAKdBNz+PewjA0MKP6iBLATHw== +"@abp/select2@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.1.0-rc.2.tgz#40c5418d007fc36817eecbe6388d767e4e7ca887" + integrity sha512-Pq0wlpL01sWRLUg5um3JtBXIqi3mmbwPwvgxP8hFbQngAt9JXAK8geNRiTMrIZgtW/ycXtM1v6I4zuWOLOeAGg== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" select2 "^4.0.13" -"@abp/sweetalert2@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.1.0-rc.1.tgz#40f4d6964cd6620dcb81040d84e432f4ae314686" - integrity sha512-E4dUC2k/kClBnadtGK5NWR9YmCGgKq5uOGjEVtzXxJH62MlaCXlcxRrK9wNzX8pzDMDYFKjuOUugoROEgbRKWQ== +"@abp/sweetalert2@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.1.0-rc.2.tgz#d35858c69e10c6726b02cdfcea88dfc32385963f" + integrity sha512-s9VPRToohN45uzHcKCF5Mcj8FVjsXcXUb0U3tuaT/Y+u4adHB3fBxYiXJFM0sVsCJ81dFktxwka40Wm8Taz/zA== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" sweetalert2 "^11.23.0" -"@abp/timeago@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.1.0-rc.1.tgz#482e3a645f63ee9adf708ef8ae895fcf2bcb4271" - integrity sha512-UOb2o6bvi3+2kbD7+rUJdqTQvTbgqUD7ktd0U5Hulb3dBOyv6MZEagzMbXA003OROiS23JzzT7M0lLStvixwlg== +"@abp/timeago@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.1.0-rc.2.tgz#98d630cc3843eee64dbcc34fb8ca5afbab034718" + integrity sha512-vJmk+otyXXJE2s2J8iYpLVaFuNAYnIUSOitmi7umYnL+k/UE2KQhBXU7FR0/OBY9mAZYd+shaiGIU1LMSaJ+Xg== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" timeago "^1.6.7" -"@abp/utils@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.1.0-rc.1.tgz#e53ef81f39fd620b41ae2bfb69d06ee85ccb28ad" - integrity sha512-xBHIw5rmAttN5PL5C84L2znHkA2HMrP6HaA2aV5k6KU7BtOWJhviZnKJgbUBfufNQNeLwlThCHAfS7aOv/UQkg== +"@abp/utils@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.1.0-rc.2.tgz#86a980c6536b3b5ce185d406723b28be421864ac" + integrity sha512-Oz863VNA8fraQ81vTvqM0IqwiaseLwfFU5QNn6iOGOfn5wQrEkPwtZ0jMI+DGNtJgPzoKiq+iKc3K+SiuVgldg== dependencies: just-compare "^2.3.0" diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo.Abp.FeatureManagement.Application.Contracts.abppkg.analyze.json b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo.Abp.FeatureManagement.Application.Contracts.abppkg.analyze.json index 73fc20b72c..fe7a26b081 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo.Abp.FeatureManagement.Application.Contracts.abppkg.analyze.json +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo.Abp.FeatureManagement.Application.Contracts.abppkg.analyze.json @@ -73,6 +73,13 @@ "contentType": "abpModule", "name": "AbpFeatureManagementApplicationContractsModule", "summary": null + }, + { + "displayName": "Manage host features", + "isEnabled": true, + "contentType": "permission", + "name": "FeatureManagement.ManageHostFeatures", + "summary": null } ] } \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo.Abp.Identity.Application.Contracts.abppkg.analyze.json b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo.Abp.Identity.Application.Contracts.abppkg.analyze.json index 8f70114751..ed9bd2c045 100644 --- a/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo.Abp.Identity.Application.Contracts.abppkg.analyze.json +++ b/modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo.Abp.Identity.Application.Contracts.abppkg.analyze.json @@ -73,6 +73,90 @@ "contentType": "abpModule", "name": "AbpIdentityApplicationContractsModule", "summary": null + }, + { + "displayName": "Role management", + "isEnabled": true, + "contentType": "permission", + "name": "AbpIdentity.Roles", + "summary": null + }, + { + "displayName": "Create", + "isEnabled": true, + "contentType": "permission", + "name": "AbpIdentity.Roles.Create", + "summary": null + }, + { + "displayName": "Edit", + "isEnabled": true, + "contentType": "permission", + "name": "AbpIdentity.Roles.Update", + "summary": null + }, + { + "displayName": "Delete", + "isEnabled": true, + "contentType": "permission", + "name": "AbpIdentity.Roles.Delete", + "summary": null + }, + { + "displayName": "Change permissions", + "isEnabled": true, + "contentType": "permission", + "name": "AbpIdentity.Roles.ManagePermissions", + "summary": null + }, + { + "displayName": "User management", + "isEnabled": true, + "contentType": "permission", + "name": "AbpIdentity.Users", + "summary": null + }, + { + "displayName": "Create", + "isEnabled": true, + "contentType": "permission", + "name": "AbpIdentity.Users.Create", + "summary": null + }, + { + "displayName": "Edit", + "isEnabled": true, + "contentType": "permission", + "name": "AbpIdentity.Users.Update", + "summary": null + }, + { + "displayName": "Manage roles", + "isEnabled": true, + "contentType": "permission", + "name": "AbpIdentity.Users.Update.ManageRoles", + "summary": null + }, + { + "displayName": "Delete", + "isEnabled": true, + "contentType": "permission", + "name": "AbpIdentity.Users.Delete", + "summary": null + }, + { + "displayName": "Change permissions", + "isEnabled": true, + "contentType": "permission", + "name": "AbpIdentity.Users.ManagePermissions", + "summary": null + }, + { + "displayName": "User lookup", + "isEnabled": true, + "contentType": "permission", + "name": "AbpIdentity.UserLookup", + "summary": null } ] } \ No newline at end of file diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityResultException.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityResultException.cs index a5b4156d2b..1ee25bcd64 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityResultException.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityResultException.cs @@ -2,18 +2,23 @@ using System.Linq; using JetBrains.Annotations; using Microsoft.AspNetCore.Identity; +using Volo.Abp.ExceptionHandling; +using Volo.Abp.Localization; namespace Volo.Abp.Identity; -public class AbpIdentityResultException : BusinessException +public class AbpIdentityResultException : BusinessException, ILocalizeErrorMessage { public IdentityResult IdentityResult { get; } public AbpIdentityResultException([NotNull] IdentityResult identityResult) - : base( - code: $"Volo.Abp.Identity:{identityResult.Errors.First().Code}", - message: identityResult.Errors.Select(err => err.Description).JoinAsString(", ")) + : base(message: identityResult.Errors.Select(err => err.Description).JoinAsString(", ")) { IdentityResult = Check.NotNull(identityResult, nameof(identityResult)); } + + public string LocalizeMessage(LocalizationContext context) + { + return Message; + } } diff --git a/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityResultException_Tests.cs b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityResultException_Tests.cs new file mode 100644 index 0000000000..fb45c5190e --- /dev/null +++ b/modules/identity/test/Volo.Abp.Identity.Domain.Tests/Volo/Abp/Identity/AbpIdentityResultException_Tests.cs @@ -0,0 +1,71 @@ +using Microsoft.AspNetCore.Identity; +using Shouldly; +using Volo.Abp.AspNetCore.ExceptionHandling; +using Volo.Abp.Localization; +using Xunit; + +namespace Volo.Abp.Identity; + +public class AbpIdentityResultException_Tests : AbpIdentityDomainTestBase +{ + [Fact] + public void Should_Localize_Messages() + { + var describer = GetRequiredService(); + var exceptionToErrorInfoConverter = GetRequiredService(); + + using (CultureHelper.Use("en")) + { + var exception = new AbpIdentityResultException( + IdentityResult.Failed( + describer.PasswordTooShort(6), + describer.PasswordRequiresNonAlphanumeric(), + describer.DefaultError()) + ); + + var message = exception.LocalizeMessage(new LocalizationContext(ServiceProvider)); + exceptionToErrorInfoConverter.Convert(exception).Message.ShouldBe(message); + + message.ShouldNotBeNull(); + message.ShouldContain("Password length must be greater than 6 characters."); + message.ShouldContain("Password must contain at least one non-alphanumeric character."); + message.ShouldContain("An unknown failure has occurred."); + } + + using (CultureHelper.Use("tr")) + { + var exception = new AbpIdentityResultException( + IdentityResult.Failed( + describer.PasswordTooShort(6), + describer.PasswordRequiresNonAlphanumeric(), + describer.DefaultError()) + ); + + var message = exception.LocalizeMessage(new LocalizationContext(ServiceProvider)); + exceptionToErrorInfoConverter.Convert(exception).Message.ShouldBe(message); + + message.ShouldNotBeNull(); + message.ShouldContain("Şifre uzunluğu 6 karakterden uzun olmalıdır."); + message.ShouldContain("Parola en az bir alfasayısal olmayan karakter içermeli"); + message.ShouldContain("Bilinmeyen bir hata oluştu."); + } + + using (CultureHelper.Use("zh-Hans")) + { + var exception = new AbpIdentityResultException( + IdentityResult.Failed( + describer.PasswordTooShort(6), + describer.PasswordRequiresNonAlphanumeric(), + describer.DefaultError()) + ); + + var message = exception.LocalizeMessage(new LocalizationContext(ServiceProvider)); + exceptionToErrorInfoConverter.Convert(exception).Message.ShouldBe(message); + + message.ShouldNotBeNull(); + message.ShouldContain("密码长度必须大于 6 字符。"); + message.ShouldContain("密码必须至少包含一个非字母数字字符。"); + message.ShouldContain("发生了一个未知错误。"); + } + } +} diff --git a/modules/openiddict/app/OpenIddict.Demo.Server/package.json b/modules/openiddict/app/OpenIddict.Demo.Server/package.json index 881359366e..52a1fc6c21 100644 --- a/modules/openiddict/app/OpenIddict.Demo.Server/package.json +++ b/modules/openiddict/app/OpenIddict.Demo.Server/package.json @@ -3,6 +3,6 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~10.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.basic": "~10.1.0-rc.2" } } diff --git a/modules/openiddict/app/angular/package.json b/modules/openiddict/app/angular/package.json index 037c330682..0eccde558f 100644 --- a/modules/openiddict/app/angular/package.json +++ b/modules/openiddict/app/angular/package.json @@ -12,15 +12,15 @@ }, "private": true, "dependencies": { - "@abp/ng.account": "~10.1.0-rc.1", - "@abp/ng.components": "~10.1.0-rc.1", - "@abp/ng.core": "~10.1.0-rc.1", - "@abp/ng.oauth": "~10.1.0-rc.1", - "@abp/ng.identity": "~10.1.0-rc.1", - "@abp/ng.setting-management": "~10.1.0-rc.1", - "@abp/ng.tenant-management": "~10.1.0-rc.1", - "@abp/ng.theme.shared": "~10.1.0-rc.1", - "@abp/ng.theme.lepton-x": "~5.1.0-rc.1", + "@abp/ng.account": "~10.1.0-rc.2", + "@abp/ng.components": "~10.1.0-rc.2", + "@abp/ng.core": "~10.1.0-rc.2", + "@abp/ng.oauth": "~10.1.0-rc.2", + "@abp/ng.identity": "~10.1.0-rc.2", + "@abp/ng.setting-management": "~10.1.0-rc.2", + "@abp/ng.tenant-management": "~10.1.0-rc.2", + "@abp/ng.theme.shared": "~10.1.0-rc.2", + "@abp/ng.theme.lepton-x": "~5.1.0-rc.2", "@angular/animations": "^15.0.1", "@angular/common": "^15.0.1", "@angular/compiler": "^15.0.1", @@ -36,7 +36,7 @@ "zone.js": "~0.11.4" }, "devDependencies": { - "@abp/ng.schematics": "~10.1.0-rc.1", + "@abp/ng.schematics": "~10.1.0-rc.2", "@angular-devkit/build-angular": "^15.0.1", "@angular-eslint/builder": "~15.1.0", "@angular-eslint/eslint-plugin": "~15.1.0", diff --git a/modules/setting-management/app/Volo.Abp.SettingManagement.DemoApp/package.json b/modules/setting-management/app/Volo.Abp.SettingManagement.DemoApp/package.json index 7ae41ebfe9..3a8321fed9 100644 --- a/modules/setting-management/app/Volo.Abp.SettingManagement.DemoApp/package.json +++ b/modules/setting-management/app/Volo.Abp.SettingManagement.DemoApp/package.json @@ -3,6 +3,6 @@ "name": "demo-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~10.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.basic": "~10.1.0-rc.2" } } diff --git a/modules/setting-management/app/Volo.Abp.SettingManagement.DemoApp/yarn.lock b/modules/setting-management/app/Volo.Abp.SettingManagement.DemoApp/yarn.lock index 55a960f9ab..a2b6e13b29 100644 --- a/modules/setting-management/app/Volo.Abp.SettingManagement.DemoApp/yarn.lock +++ b/modules/setting-management/app/Volo.Abp.SettingManagement.DemoApp/yarn.lock @@ -2,185 +2,185 @@ # yarn lockfile v1 -"@abp/aspnetcore.mvc.ui.theme.basic@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-10.1.0-rc.1.tgz#99407c4108b685eef91f21d038cc5bd2343b5847" - integrity sha512-UbVgYf1K+zUqueNCwOl4QoUAz7nwJyoQJSqq0gi7LA9JBRBbb8nDA8szQjAUVqF4h+k4o61588MUQnerUQlZ6g== +"@abp/aspnetcore.mvc.ui.theme.basic@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-10.1.0-rc.2.tgz#599f5c47a417d1230fc17c0446a0229f920f7246" + integrity sha512-8F4nEK+VtgRRf8n+66HMbtCEaOMCW/OdbSEWRl9ahMNoj860oPIJ8P8Qn/2+LjtkPMdDAfCdEzyDzCd3igaFaA== dependencies: - "@abp/aspnetcore.mvc.ui.theme.shared" "~10.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.shared" "~10.1.0-rc.2" -"@abp/aspnetcore.mvc.ui.theme.shared@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.1.0-rc.1.tgz#7c0f721bdd4ec99f441a49e662a457d98c2c66fa" - integrity sha512-dB6wmR3C43Vn1/hiWY3IkxivUEWTGhO4mj3hffwAeyJqnEUh6kQUW6UfmYNGEhahSjhVIS3lUZ4x+R+yGCpemw== +"@abp/aspnetcore.mvc.ui.theme.shared@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-10.1.0-rc.2.tgz#e5056e4e159f5815e3cffecab5c46f3d7d4f79d7" + integrity sha512-bo56XzQZPYL/3ckWTTTSSUsSFSFJobvfE29cz13NIrZ/tBtWyQCAJn92wYHuY+6IezYUWb4ga3PkFeHRzR142A== dependencies: - "@abp/aspnetcore.mvc.ui" "~10.1.0-rc.1" - "@abp/bootstrap" "~10.1.0-rc.1" - "@abp/bootstrap-datepicker" "~10.1.0-rc.1" - "@abp/bootstrap-daterangepicker" "~10.1.0-rc.1" - "@abp/datatables.net-bs5" "~10.1.0-rc.1" - "@abp/font-awesome" "~10.1.0-rc.1" - "@abp/jquery-form" "~10.1.0-rc.1" - "@abp/jquery-validation-unobtrusive" "~10.1.0-rc.1" - "@abp/lodash" "~10.1.0-rc.1" - "@abp/luxon" "~10.1.0-rc.1" - "@abp/malihu-custom-scrollbar-plugin" "~10.1.0-rc.1" - "@abp/moment" "~10.1.0-rc.1" - "@abp/select2" "~10.1.0-rc.1" - "@abp/sweetalert2" "~10.1.0-rc.1" - "@abp/timeago" "~10.1.0-rc.1" - -"@abp/aspnetcore.mvc.ui@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.1.0-rc.1.tgz#290297353fdc15826a870b35162a0bbf4c9bc948" - integrity sha512-RBR5wPf5ygzuSpfXG5MeOGd/YOtQH5pVyU65T4DMtGpLKHQxgxbh7JK1btyC5zGmzr/Ds5+5Mcd7S66dIckDJQ== + "@abp/aspnetcore.mvc.ui" "~10.1.0-rc.2" + "@abp/bootstrap" "~10.1.0-rc.2" + "@abp/bootstrap-datepicker" "~10.1.0-rc.2" + "@abp/bootstrap-daterangepicker" "~10.1.0-rc.2" + "@abp/datatables.net-bs5" "~10.1.0-rc.2" + "@abp/font-awesome" "~10.1.0-rc.2" + "@abp/jquery-form" "~10.1.0-rc.2" + "@abp/jquery-validation-unobtrusive" "~10.1.0-rc.2" + "@abp/lodash" "~10.1.0-rc.2" + "@abp/luxon" "~10.1.0-rc.2" + "@abp/malihu-custom-scrollbar-plugin" "~10.1.0-rc.2" + "@abp/moment" "~10.1.0-rc.2" + "@abp/select2" "~10.1.0-rc.2" + "@abp/sweetalert2" "~10.1.0-rc.2" + "@abp/timeago" "~10.1.0-rc.2" + +"@abp/aspnetcore.mvc.ui@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-10.1.0-rc.2.tgz#e25d3575d40bfcb3f809bd2d355671181ee5ff40" + integrity sha512-MOF86bVbi7N/nIla+361nsBrN4tiSka8xzpWcgqlLcCAl9ILG4rugbtafBAjN81taPma2peZM7egaOR4SDkTMw== dependencies: ansi-colors "^4.1.3" -"@abp/bootstrap-datepicker@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.1.0-rc.1.tgz#54730dc001dab746af18c41d51b73949742e3b2d" - integrity sha512-DhVfA9WjfRQrUwjN2eJTCjLEhAU7rLjWgMlifW1id3HbdMs9emKn0rzQqhZ8MOSUNBwLkbUWcXWnojRUFgUB+w== +"@abp/bootstrap-datepicker@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-datepicker/-/bootstrap-datepicker-10.1.0-rc.2.tgz#be80c6104ba53e18935fbf62ca2c1890f4b2fde4" + integrity sha512-BNcDYUSbZaLah4SfXm0efoqFTsOViVm6370k9L7vix/OGpIWwklJsr8y78lvdM5ANgNCfl0LPSq+seLJFc/OLA== dependencies: bootstrap-datepicker "^1.10.1" -"@abp/bootstrap-daterangepicker@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.1.0-rc.1.tgz#9af7ffe1d1bd4fc99e1606e30189e6f28bb2f201" - integrity sha512-/Hsge8UwLZFuPNWRZhpf4nVB4urPYsaKAuCz0pSH5OHmS4FWr0Ng3vnUd9fcSeaq9OV2HarcgGWDapYtszttVQ== +"@abp/bootstrap-daterangepicker@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/bootstrap-daterangepicker/-/bootstrap-daterangepicker-10.1.0-rc.2.tgz#f189f7d070ebd97d9cfdcb99571cab2d6a198ab5" + integrity sha512-bV8J0MuiAFVLkr48JsB6aZU6aPoqw+Gyhq1szQ74bEwNQlRBPuF92WVA5FACaUBj8dMUzR9HDDAYQuxUzpKYKA== dependencies: bootstrap-daterangepicker "^3.1.0" -"@abp/bootstrap@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.1.0-rc.1.tgz#e937f0a10874c0f3b0f719ff6a316b0ca91e65c5" - integrity sha512-GlRyH5oCjcmFk1eBGCHk6GglL2DJ5RLlDXn7HWU675u48DCQTkBbKP9qA3RcYERQB4qqhcOKEDtYPZVljRVHRQ== +"@abp/bootstrap@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/bootstrap/-/bootstrap-10.1.0-rc.2.tgz#2300800a29ea09b91f5ed2e6177e5921fe7d2a0f" + integrity sha512-K+tDI9vz/Y9B/yu0i3AVpm4v3Odi44Q/yH5hAprL7f4pGxEOiqAFB/qzHAxG+7Oa7wjv5tPLv+Cz4DavBQjd8Q== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" bootstrap "^5.3.8" -"@abp/core@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.1.0-rc.1.tgz#d9ce0958407fa39c8015eb2d9328a3a157aaa4f0" - integrity sha512-pFsaQei1W0JRJkhNdlxq+EDP7x+83nT0UHZetTHNzp8tkP4Cn8Ni/ZODIUku8ck1vcKc0X3pXKj42BN+yZ1g3g== +"@abp/core@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/core/-/core-10.1.0-rc.2.tgz#403687aff5a30788f7b7ca660abdfd85d89438aa" + integrity sha512-euuG2Hna/DT6/R1dGOjgp3vcehYtF+CcOkRj31oquYKaM5YWk4OaZ314DSpnjgs/xo8DuVc4eKFQwIxD9RK41w== dependencies: - "@abp/utils" "~10.1.0-rc.1" + "@abp/utils" "~10.1.0-rc.2" -"@abp/datatables.net-bs5@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.1.0-rc.1.tgz#7981bc64c543d24aa5b0ab373f5693187990d5be" - integrity sha512-yaYHuZ9eVHT2cZcH9r680X+imWhOqbhWlOlbLDpJtg7Un0ejDzou+JqxKYHHXmSEKYEPHjfCEhnXkMnyqvSUHQ== +"@abp/datatables.net-bs5@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/datatables.net-bs5/-/datatables.net-bs5-10.1.0-rc.2.tgz#a60650d1802b40751d30f8f6c56beb23fd66481b" + integrity sha512-IWwexNqbMpET54Fvm9LoPTJYf+4CoBbjFOvz3sL6CgO2feV5R5fKigjVU8zXKNh2W+RG8L6zEarfVxrr114TsA== dependencies: - "@abp/datatables.net" "~10.1.0-rc.1" + "@abp/datatables.net" "~10.1.0-rc.2" datatables.net-bs5 "^2.3.4" -"@abp/datatables.net@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.1.0-rc.1.tgz#f487357690060dd055dbb7f54e1298e8d8a81e40" - integrity sha512-KTKTW73H9vaX6J9ipXUf7d3QdseKaFRzl7e1QKldOoQxBs4pzT2NATGdUyiAnJsfPl4scpSFetzI66Rs7r3idg== +"@abp/datatables.net@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/datatables.net/-/datatables.net-10.1.0-rc.2.tgz#9147f68bc6dbc4eb40a9ddf65c7859e788cdcac2" + integrity sha512-a9DJpwg14S4nVOiC4ipw0CQwEYWB602e2gCJiH7W1mxopbQb135RxwhtdTnW//eIONcxC9IrEuvcBEAUVt2B7Q== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" datatables.net "^2.3.4" -"@abp/font-awesome@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.1.0-rc.1.tgz#2ee67b07ce6296c7f0ac5217c9d9677b9c70f05e" - integrity sha512-O/3oLbSu1pMNftQzQW29gSpkWCFJazFKjT7sOYId1Ueric0svyJ42hFr5gHf9RFVmCU5pgyhawcoTyzQ34PxYQ== +"@abp/font-awesome@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/font-awesome/-/font-awesome-10.1.0-rc.2.tgz#364466cfe67e41e0c4d16b57d3923d10f66369f1" + integrity sha512-F1Jy8xoFV2aA+VN+NH1gtrG96/j9w7Picc+KLoCoIyNnJr/xJur11XkJyu5ln8KF4V7p/DY7QaQodWV/btOs8g== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" "@fortawesome/fontawesome-free" "^7.0.1" -"@abp/jquery-form@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-10.1.0-rc.1.tgz#60fc98085671a2ff08e8c411847354b6a48cf55e" - integrity sha512-bj70hE7MsH2AoBb2yU2d6ng5Bo/wqjCNUI0kbG0T1T4yp1cToGAVJ6hrd+KOp2Tf6W+NITn1EycxRTCktE9QRA== +"@abp/jquery-form@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery-form/-/jquery-form-10.1.0-rc.2.tgz#3857717d07569c22d4bbbe459238abeb816d606a" + integrity sha512-2D5WHVnfK9bhRces1tgPwOEoc7KCYKYiKHBOcqct+LTA7zoRjJv/PM8/JhFVl+grVIw1aSwO4tU3YfZ22Vxipg== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" jquery-form "^4.3.0" -"@abp/jquery-validation-unobtrusive@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.1.0-rc.1.tgz#a820989d8daf53096f838817cb2b3dba21f71653" - integrity sha512-7uXxbvuZ+ecD5suH3nx/ggiFAA8GjXGAB5tuGCdqePdBn1PMzDIDI1ZP9q8sR7sMjo7RKVK6MzX+PazUuJ4L7g== +"@abp/jquery-validation-unobtrusive@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-10.1.0-rc.2.tgz#efd7b69a078a20c0bf405408dbdf52a7bf770b3b" + integrity sha512-tZ0MWgzBqp+SNfMxM0z2cGB21NiTHuVJyyQaXKE/ptuD5pc0uRkcqw/J2kWfiqsoVgChz27IB6h8/jqDafS4qg== dependencies: - "@abp/jquery-validation" "~10.1.0-rc.1" + "@abp/jquery-validation" "~10.1.0-rc.2" jquery-validation-unobtrusive "^4.0.0" -"@abp/jquery-validation@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.1.0-rc.1.tgz#c8630b0a53ee93218be5c82264085b36b0b07936" - integrity sha512-/eS4vy5gwHIaRr6aHkvO6xWRjL22bVly7S0AqbZw2OxJ0pw1n23n+zipn/NDRSngRZSajTLLm1pYCG5qlJGd3A== +"@abp/jquery-validation@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery-validation/-/jquery-validation-10.1.0-rc.2.tgz#d39537a7356c51f9db2e66f6740cf6df86bd0442" + integrity sha512-LOkS0NKk4pLtLjPU0CCbwROyUg6EtJN8Z/it7QuKK1CIRfYYcAStgNnNm5geZP7CqECIkoiFfgWjI+L5Z9/Tfg== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" jquery-validation "^1.21.0" -"@abp/jquery@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.1.0-rc.1.tgz#38d5d5b5a6144bf207773ef26f36d381a257a635" - integrity sha512-YarNlYPG7MfLc0fSnOcVrp9Da1EUxz4zUERnTOQ0/EdOUdcFhGAqI2yfoTFCxqX+TSphQWZEdif/tsfULtkI1A== +"@abp/jquery@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/jquery/-/jquery-10.1.0-rc.2.tgz#101a55f70d510978c8c05f5857d0e9d4965263f7" + integrity sha512-bQV1uFWGtwRYjNOsqJ8FM2004idX2Jj7YVL19YF1/PjyPUSMX+s8/IvJizBjyY5hPAiWBBhmV9g+IFWzxlDQoQ== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" jquery "~3.7.1" -"@abp/lodash@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.1.0-rc.1.tgz#bf735e34a42ddcdf19e331c8c5cf1a24a87475cb" - integrity sha512-JQV0oFLE90V9GdrD37PTMiOylw/U6yjpDu5PblVW/zlNmw/TzEtukp3/vJl7GJxoKRYWtgJf1nFLgG/+2kjE1A== +"@abp/lodash@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/lodash/-/lodash-10.1.0-rc.2.tgz#d08c03f8d3d0fbaa3e71e603cbe5fb7f176933ef" + integrity sha512-KCnD1p2y52ZI+2ifpiFIUAiDPsKehnOD8HV5qKeObO6UCP97okif8IP+sQDmNQb8O33y/NKTyx/HcpwBbe/NYQ== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" lodash "^4.17.21" -"@abp/luxon@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.1.0-rc.1.tgz#1c883ad3a5653f0c6ce4965e4b4ce21543129534" - integrity sha512-SDtwjIDR4OzrtzRWpgb8atUSK0DwajJ5LC486xHq5ftnbFETRfOwihyIfC2H4oIPPDE5oS+LOnjCkRYVkGt0oQ== +"@abp/luxon@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/luxon/-/luxon-10.1.0-rc.2.tgz#ef8d2b323bac054fc9610e241e1b1763d229e065" + integrity sha512-qYFl6XO3g9mZiu0dtIczI7LRuYWwc+RkpbDzSmruXcRks3KA+ZZco2vhHNnlwtXcINl/TXtbW7Wc0MX+8IB1Kw== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" luxon "^3.7.2" -"@abp/malihu-custom-scrollbar-plugin@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.1.0-rc.1.tgz#770106795f5eba9d36dd2d4002e450c215c66787" - integrity sha512-EFwQ7rUiPM6mxznn1t23ySzQH+VQ0dB2YxOqBAluFR/yC3qypjb98jGQaZKsJx9+iT4DuG9gGPKOfct1Sqh0KQ== +"@abp/malihu-custom-scrollbar-plugin@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-10.1.0-rc.2.tgz#dfaf666442c7c122f7da72c83b9adf194d5b6ec8" + integrity sha512-PudMHmNQgZ6JZeaVt1ZoXLqO0UZXJzUYiBah2LDkC4EMLjnMJFINHBoEVVa4ooXH0yjFv+zsbN0vWZYJ8TBJIA== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" malihu-custom-scrollbar-plugin "^3.1.5" -"@abp/moment@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.1.0-rc.1.tgz#fe5bfa5bc28bb847cf801523c1b2f118ffb12447" - integrity sha512-P6z9YO/FC/5DhTDWsHkquo5tEzIhSfUnRefLcZtv7XuJtevH/93NFSr23RL6D+F4r3cB5GMXGFUYmXGKWoXGHg== +"@abp/moment@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/moment/-/moment-10.1.0-rc.2.tgz#610a1592d13984aea51abbd13df8c5995a089149" + integrity sha512-ep8PnAXARw0t/wtGOVp/oiNhF3B0Bh6y2vRzKrcSoyXAQREGGm4fJdZVYZLGTfI4lFLTjebEgf4O7T9feUwJAw== dependencies: moment "^2.30.1" -"@abp/select2@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.1.0-rc.1.tgz#19432bb6483f8a3470f02a3037612835b4c54ef4" - integrity sha512-xPvdrgXZzRhEIrEyw9eoITc+hYlbi8fSW3BGQ1ZMBXWgYkjWxhj/1AuLsu2ewFAKdBNz+PewjA0MKP6iBLATHw== +"@abp/select2@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/select2/-/select2-10.1.0-rc.2.tgz#40c5418d007fc36817eecbe6388d767e4e7ca887" + integrity sha512-Pq0wlpL01sWRLUg5um3JtBXIqi3mmbwPwvgxP8hFbQngAt9JXAK8geNRiTMrIZgtW/ycXtM1v6I4zuWOLOeAGg== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" select2 "^4.0.13" -"@abp/sweetalert2@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.1.0-rc.1.tgz#40f4d6964cd6620dcb81040d84e432f4ae314686" - integrity sha512-E4dUC2k/kClBnadtGK5NWR9YmCGgKq5uOGjEVtzXxJH62MlaCXlcxRrK9wNzX8pzDMDYFKjuOUugoROEgbRKWQ== +"@abp/sweetalert2@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/sweetalert2/-/sweetalert2-10.1.0-rc.2.tgz#d35858c69e10c6726b02cdfcea88dfc32385963f" + integrity sha512-s9VPRToohN45uzHcKCF5Mcj8FVjsXcXUb0U3tuaT/Y+u4adHB3fBxYiXJFM0sVsCJ81dFktxwka40Wm8Taz/zA== dependencies: - "@abp/core" "~10.1.0-rc.1" + "@abp/core" "~10.1.0-rc.2" sweetalert2 "^11.23.0" -"@abp/timeago@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.1.0-rc.1.tgz#482e3a645f63ee9adf708ef8ae895fcf2bcb4271" - integrity sha512-UOb2o6bvi3+2kbD7+rUJdqTQvTbgqUD7ktd0U5Hulb3dBOyv6MZEagzMbXA003OROiS23JzzT7M0lLStvixwlg== +"@abp/timeago@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/timeago/-/timeago-10.1.0-rc.2.tgz#98d630cc3843eee64dbcc34fb8ca5afbab034718" + integrity sha512-vJmk+otyXXJE2s2J8iYpLVaFuNAYnIUSOitmi7umYnL+k/UE2KQhBXU7FR0/OBY9mAZYd+shaiGIU1LMSaJ+Xg== dependencies: - "@abp/jquery" "~10.1.0-rc.1" + "@abp/jquery" "~10.1.0-rc.2" timeago "^1.6.7" -"@abp/utils@~10.1.0-rc.1": - version "10.1.0-rc.1" - resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.1.0-rc.1.tgz#e53ef81f39fd620b41ae2bfb69d06ee85ccb28ad" - integrity sha512-xBHIw5rmAttN5PL5C84L2znHkA2HMrP6HaA2aV5k6KU7BtOWJhviZnKJgbUBfufNQNeLwlThCHAfS7aOv/UQkg== +"@abp/utils@~10.1.0-rc.2": + version "10.1.0-rc.2" + resolved "https://registry.yarnpkg.com/@abp/utils/-/utils-10.1.0-rc.2.tgz#86a980c6536b3b5ce185d406723b28be421864ac" + integrity sha512-Oz863VNA8fraQ81vTvqM0IqwiaseLwfFU5QNn6iOGOfn5wQrEkPwtZ0jMI+DGNtJgPzoKiq+iKc3K+SiuVgldg== dependencies: just-compare "^2.3.0" diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Application.Contracts/Volo.Abp.SettingManagement.Application.Contracts.abppkg.analyze.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Application.Contracts/Volo.Abp.SettingManagement.Application.Contracts.abppkg.analyze.json index f07fc5a44c..a366802a26 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Application.Contracts/Volo.Abp.SettingManagement.Application.Contracts.abppkg.analyze.json +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Application.Contracts/Volo.Abp.SettingManagement.Application.Contracts.abppkg.analyze.json @@ -68,6 +68,27 @@ "contentType": "abpModule", "name": "AbpSettingManagementApplicationContractsModule", "summary": null + }, + { + "displayName": "Emailing", + "isEnabled": true, + "contentType": "permission", + "name": "SettingManagement.Emailing", + "summary": null + }, + { + "displayName": "Emailing test", + "isEnabled": true, + "contentType": "permission", + "name": "SettingManagement.Emailing.Test", + "summary": null + }, + { + "displayName": "Time zone", + "isEnabled": true, + "contentType": "permission", + "name": "SettingManagement.TimeZone", + "summary": null } ] } \ No newline at end of file diff --git a/modules/virtual-file-explorer/app/package.json b/modules/virtual-file-explorer/app/package.json index 9087a91d1f..50866814bc 100644 --- a/modules/virtual-file-explorer/app/package.json +++ b/modules/virtual-file-explorer/app/package.json @@ -3,7 +3,7 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~10.1.0-rc.1", - "@abp/virtual-file-explorer": "~10.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.basic": "~10.1.0-rc.2", + "@abp/virtual-file-explorer": "~10.1.0-rc.2" } } diff --git a/npm/lerna.json b/npm/lerna.json index 870bb88959..ee02c6794e 100644 --- a/npm/lerna.json +++ b/npm/lerna.json @@ -1,5 +1,5 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "packages": [ "packs/*" ], diff --git a/npm/ng-packs/.gitignore b/npm/ng-packs/.gitignore index 9d756a8b67..84b48a798c 100644 --- a/npm/ng-packs/.gitignore +++ b/npm/ng-packs/.gitignore @@ -58,3 +58,5 @@ Thumbs.db .angular .nx/ + +vitest.config.*.timestamp* \ No newline at end of file diff --git a/npm/ng-packs/apps/dev-app/project.json b/npm/ng-packs/apps/dev-app/project.json index bea3bf6612..c397d8d449 100644 --- a/npm/ng-packs/apps/dev-app/project.json +++ b/npm/ng-packs/apps/dev-app/project.json @@ -175,10 +175,12 @@ "executor": "@nx/eslint:lint" }, "test": { - "executor": "@nx/jest:jest", + "executor": "@nx/vitest:test", "outputs": ["{workspaceRoot}/coverage/apps/dev-app"], "options": { - "jestConfig": "apps/dev-app/jest.config.ts" + "passWithNoTests": true, + "reportsDirectory": "../../coverage/apps/dev-app", + "silent": false } }, "serve-static": { diff --git a/npm/ng-packs/apps/dev-app/src/app/app.routes.ts b/npm/ng-packs/apps/dev-app/src/app/app.routes.ts index 47462b7f8f..c520328975 100644 --- a/npm/ng-packs/apps/dev-app/src/app/app.routes.ts +++ b/npm/ng-packs/apps/dev-app/src/app/app.routes.ts @@ -6,6 +6,10 @@ export const appRoutes: Routes = [ pathMatch: 'full', loadComponent: () => import('./home/home.component').then(m => m.HomeComponent), }, + { + path: 'dynamic-form', + loadComponent: () => import('./dynamic-form-page/dynamic-form-page.component').then(m => m.DynamicFormPageComponent), + }, { path: 'account', loadChildren: () => import('@abp/ng.account').then(m => m.createRoutes()), diff --git a/npm/ng-packs/apps/dev-app/src/app/dynamic-form-page/dynamic-form-page.component.html b/npm/ng-packs/apps/dev-app/src/app/dynamic-form-page/dynamic-form-page.component.html new file mode 100644 index 0000000000..c675dca707 --- /dev/null +++ b/npm/ng-packs/apps/dev-app/src/app/dynamic-form-page/dynamic-form-page.component.html @@ -0,0 +1,111 @@ +
+
+
+

Dynamic Form Showcase

+

+ Comprehensive example demonstrating all available field types with validation and conditional logic. +

+
+
+
+ +
+
+
+
+

+ + User Registration Form +

+ All 16 field types + Nested Forms (Group & Array) with full accessibility support +
+
+ @if (formFields.length) { + + } @else { +
+
+ Loading... +
+

Loading form configuration...

+
+ } +
+
+ + +
+
+
+ + Available Field Types +
+
+
+
+
+
Text Inputs
+
    +
  • Text
  • +
  • Email
  • +
  • Password
  • +
  • Tel
  • +
  • URL
  • +
  • Textarea
  • +
+
+
+
Special Inputs
+
    +
  • Number
  • +
  • Date
  • +
  • DateTime-Local
  • +
  • Time
  • +
  • Range
  • +
  • Color
  • +
+
+
+
Selection
+
    +
  • Select (Dropdown)
  • +
  • Radio
  • +
  • Checkbox
  • +
+
+
+
Files
+
    +
  • File (Single/Multiple)
  • +
+
+
+
Nested Forms
+
    +
  • Group (Nested Fields)
  • +
  • Array (Dynamic List)
  • +
+
+
+
+
+ + Features: Full validation support, conditional logic, nested forms (groups & arrays), + grid-based layout, ARIA accessibility, keyboard navigation, and screen reader support. +
+
+
+ + New: Try the nested forms below! Add/remove phone numbers dynamically, + or fill in your work experience. The Address section shows a grouped form. +
+
+
+
+
+
\ No newline at end of file diff --git a/npm/ng-packs/apps/dev-app/src/app/dynamic-form-page/dynamic-form-page.component.ts b/npm/ng-packs/apps/dev-app/src/app/dynamic-form-page/dynamic-form-page.component.ts new file mode 100644 index 0000000000..0955a448e5 --- /dev/null +++ b/npm/ng-packs/apps/dev-app/src/app/dynamic-form-page/dynamic-form-page.component.ts @@ -0,0 +1,38 @@ +import { Component, inject, OnInit, ViewChild } from '@angular/core'; +import { DynamicFormComponent, FormFieldConfig } from '@abp/ng.components/dynamic-form'; +import { FormConfigService } from './form-config.service'; + +@Component({ + selector: 'app-dynamic-form-page', + templateUrl: './dynamic-form-page.component.html', + imports: [DynamicFormComponent], +}) +export class DynamicFormPageComponent implements OnInit { + @ViewChild(DynamicFormComponent, { static: false }) dynamicFormComponent: DynamicFormComponent; + protected readonly formConfigService = inject(FormConfigService); + + formFields: FormFieldConfig[] = []; + + ngOnInit() { + this.formConfigService.getFormConfig().subscribe(config => { + this.formFields = config; + }); + } + + submit(formData: any) { + console.log('✅ Form Submitted Successfully!', formData); + console.table(formData); + + // Show success message + alert('✅ Form submitted successfully! Check the console for details.'); + + // Reset form after submission + this.dynamicFormComponent.resetForm(); + } + + cancel() { + console.log('❌ Form Cancelled'); + alert('Form cancelled'); + this.dynamicFormComponent.resetForm(); + } +} diff --git a/npm/ng-packs/apps/dev-app/src/app/dynamic-form-page/form-config.service.ts b/npm/ng-packs/apps/dev-app/src/app/dynamic-form-page/form-config.service.ts new file mode 100644 index 0000000000..b88423f945 --- /dev/null +++ b/npm/ng-packs/apps/dev-app/src/app/dynamic-form-page/form-config.service.ts @@ -0,0 +1,381 @@ +import { Injectable } from '@angular/core'; +import { FormFieldConfig } from '@abp/ng.components/dynamic-form'; +import { Observable, of } from 'rxjs'; + +@Injectable({ + providedIn: 'root', +}) +export class FormConfigService { + getFormConfig(): Observable { + const formConfig: FormFieldConfig[] = [ + // Section 1: Basic Text Inputs + { + key: 'firstName', + label: 'First Name', + type: 'text', + placeholder: 'Enter your first name', + required: true, + gridSize: 6, + order: 1, + validators: [{ type: 'required', message: 'First name is required' }], + }, + { + key: 'lastName', + label: 'Last Name', + type: 'text', + placeholder: 'Enter your last name', + required: true, + gridSize: 6, + order: 2, + validators: [{ type: 'required', message: 'Last name is required' }], + }, + + // Section 2: Email & Password + { + key: 'email', + label: 'Email Address', + type: 'email', + placeholder: 'example@domain.com', + required: true, + gridSize: 6, + order: 3, + validators: [ + { type: 'required', message: 'Email is required' }, + { type: 'email', message: 'Invalid email address' }, + ], + }, + { + key: 'password', + label: 'Password', + type: 'password', + placeholder: 'Enter a strong password', + required: true, + gridSize: 6, + order: 4, + minLength: 8, + maxLength: 50, + validators: [ + { type: 'required', message: 'Password is required' }, + { type: 'minLength', value: 8, message: 'Password must be at least 8 characters' }, + ], + }, + + // Section 3: Contact Information + { + key: 'phone', + label: 'Phone Number', + type: 'tel', + placeholder: '555-123-4567', + gridSize: 6, + order: 5, + pattern: '[0-9]{3}-[0-9]{3}-[0-9]{4}', + }, + { + key: 'website', + label: 'Website', + type: 'url', + placeholder: 'https://example.com', + gridSize: 6, + order: 6, + }, + + // Section 4: Numbers & Dates + { + key: 'age', + label: 'Age', + type: 'number', + placeholder: 'Enter your age', + required: true, + gridSize: 4, + order: 7, + min: 18, + max: 100, + validators: [ + { type: 'required', message: 'Age is required' }, + { type: 'min', value: 18, message: 'You must be at least 18 years old' }, + ], + }, + { + key: 'birthdate', + label: 'Birth Date', + type: 'date', + required: true, + gridSize: 4, + order: 8, + max: new Date().toISOString().split('T')[0], + validators: [{ type: 'required', message: 'Birth date is required' }], + }, + { + key: 'appointmentTime', + label: 'Appointment Date & Time', + type: 'datetime-local', + gridSize: 4, + order: 9, + min: new Date().toISOString().slice(0, 16), + }, + + // Section 5: Select & Radio + { + key: 'country', + label: 'Country', + type: 'select', + required: true, + gridSize: 6, + order: 10, + options: { + defaultValues: [ + { key: 'usa', value: 'United States' }, + { key: 'uk', value: 'United Kingdom' }, + { key: 'canada', value: 'Canada' }, + { key: 'germany', value: 'Germany' }, + { key: 'france', value: 'France' }, + ], + }, + validators: [{ type: 'required', message: 'Country is required' }], + }, + { + key: 'gender', + label: 'Gender', + type: 'radio', + required: true, + gridSize: 6, + order: 11, + options: { + defaultValues: [ + { key: 'male', value: 'Male' }, + { key: 'female', value: 'Female' }, + { key: 'other', value: 'Other' }, + { key: 'prefer-not-to-say', value: 'Prefer not to say' }, + ], + }, + validators: [{ type: 'required', message: 'Gender is required' }], + }, + + // Section 6: Conditional Field (shown when country is USA) + { + key: 'state', + label: 'State (USA Only)', + type: 'select', + gridSize: 6, + order: 12, + options: { + defaultValues: [ + { key: 'ca', value: 'California' }, + { key: 'ny', value: 'New York' }, + { key: 'tx', value: 'Texas' }, + { key: 'fl', value: 'Florida' }, + ], + }, + conditionalLogic: [ + { dependsOn: 'country', condition: 'equals', value: 'usa', action: 'show' }, + ], + }, + + // Section 7: Time & Range + { + key: 'preferredTime', + label: 'Preferred Contact Time', + type: 'time', + gridSize: 6, + order: 13, + step: '900', // 15 minutes + }, + { + key: 'experienceLevel', + label: 'Experience Level (0-10)', + type: 'range', + gridSize: 6, + order: 14, + min: 0, + max: 10, + step: 1, + value: 5, + }, + + // Section 8: Color & File + { + key: 'favoriteColor', + label: 'Favorite Color', + type: 'color', + gridSize: 6, + order: 15, + value: '#007bff', + }, + { + key: 'profilePicture', + label: 'Profile Picture', + type: 'file', + gridSize: 6, + order: 16, + accept: 'image/*', + multiple: false, + }, + + // Section 9: Textarea & Checkbox + { + key: 'bio', + label: 'Biography', + type: 'textarea', + placeholder: 'Tell us about yourself...', + gridSize: 12, + order: 17, + maxLength: 500, + }, + { + key: 'newsletter', + label: 'Subscribe to newsletter', + type: 'checkbox', + gridSize: 6, + order: 18, + }, + { + key: 'terms', + label: 'I agree to the terms and conditions', + type: 'checkbox', + required: true, + gridSize: 6, + order: 19, + validators: [{ type: 'requiredTrue', message: 'You must agree to the terms' }], + }, + + // Section 10: NESTED FORM - Phone Numbers (Array) + { + key: 'phoneNumbers', + type: 'array', + label: 'Phone Numbers', + gridSize: 12, + order: 20, + minItems: 1, + maxItems: 5, + children: [ + { + key: 'type', + type: 'select', + label: 'Type', + gridSize: 4, + required: true, + options: { + defaultValues: [ + { key: 'mobile', value: 'Mobile' }, + { key: 'home', value: 'Home' }, + { key: 'work', value: 'Work' }, + { key: 'other', value: 'Other' } + ] + }, + validators: [{ type: 'required', message: 'Phone type is required' }], + }, + { + key: 'number', + type: 'tel', + label: 'Number', + gridSize: 8, + required: true, + placeholder: '555-123-4567', + validators: [{ type: 'required', message: 'Phone number is required' }], + }, + ] + }, + + // Section 11: NESTED FORM - Work Experience (Array with nested group) + { + key: 'workExperience', + type: 'array', + label: 'Work Experience', + gridSize: 12, + order: 21, + minItems: 0, + maxItems: 10, + children: [ + { + key: 'company', + type: 'text', + label: 'Company Name', + gridSize: 6, + required: true, + validators: [{ type: 'required', message: 'Company name is required' }], + }, + { + key: 'position', + type: 'text', + label: 'Position', + gridSize: 6, + required: true, + validators: [{ type: 'required', message: 'Position is required' }], + }, + { + key: 'startDate', + type: 'date', + label: 'Start Date', + gridSize: 6, + required: true, + validators: [{ type: 'required', message: 'Start date is required' }], + }, + { + key: 'endDate', + type: 'date', + label: 'End Date', + gridSize: 6, + }, + { + key: 'currentJob', + type: 'checkbox', + label: 'Currently working here', + gridSize: 12, + }, + { + key: 'description', + type: 'textarea', + label: 'Job Description', + placeholder: 'Describe your responsibilities...', + gridSize: 12, + maxLength: 500, + }, + ] + }, + + // Section 12: NESTED FORM - Address Group (Group type) + { + key: 'address', + type: 'group', + label: 'Address Information', + gridSize: 12, + order: 22, + children: [ + { + key: 'street', + type: 'text', + label: 'Street Address', + gridSize: 8, + placeholder: '123 Main St', + }, + { + key: 'apartment', + type: 'text', + label: 'Apt/Suite', + gridSize: 4, + placeholder: 'Apt 4B', + }, + { + key: 'city', + type: 'text', + label: 'City', + gridSize: 6, + required: true, + validators: [{ type: 'required', message: 'City is required' }], + }, + { + key: 'zipCode', + type: 'text', + label: 'ZIP Code', + gridSize: 6, + required: true, + pattern: '[0-9]{5}', + validators: [{ type: 'required', message: 'ZIP code is required' }], + }, + ] + }, + ]; + + return of(formConfig); + } +} diff --git a/npm/ng-packs/apps/dev-app/src/app/home/home.component.html b/npm/ng-packs/apps/dev-app/src/app/home/home.component.html index 1a0d46c2da..d83f2be7f1 100644 --- a/npm/ng-packs/apps/dev-app/src/app/home/home.component.html +++ b/npm/ng-packs/apps/dev-app/src/app/home/home.component.html @@ -1,4 +1,7 @@
+
+

{{ '::Welcome' | abpLocalization }}

{{ '::LongWelcomeMessage' | abpLocalization }}

@if (!hasLoggedIn) { - - {{ 'AbpAccount::Login' | abpLocalization }} - + + {{ 'AbpAccount::Login' | abpLocalization }} + }
@@ -30,8 +28,7 @@
- + "> - + "> - + ">
- + ">

- - + + "> - + style="width: 72px; height: 28px; border: none; display: inline-block">

- + "> - + ">
@@ -199,8 +179,7 @@

- + "> - + "> - + "> - + "> - + "> - + ">
@@ -301,12 +263,11 @@

@if (context.customTemplate) { - + } @for (link of context.links; track $index) { - {{ link.label }} + {{ link.label }} }
@@ -318,9 +279,8 @@
- Details + Details
@@ -340,4 +300,4 @@ border-left: 0 !important; } } - + \ No newline at end of file diff --git a/npm/ng-packs/apps/dev-app/src/app/home/home.component.ts b/npm/ng-packs/apps/dev-app/src/app/home/home.component.ts index 44bf9f269e..aa25b046d7 100644 --- a/npm/ng-packs/apps/dev-app/src/app/home/home.component.ts +++ b/npm/ng-packs/apps/dev-app/src/app/home/home.component.ts @@ -2,16 +2,24 @@ import { AuthService, LocalizationPipe } from '@abp/ng.core'; import { Component, inject } from '@angular/core'; import { NgTemplateOutlet } from '@angular/common'; import { ButtonComponent, CardBodyComponent, CardComponent } from '@abp/ng.theme.shared'; +import { RouterLink } from '@angular/router'; @Component({ selector: 'app-home', templateUrl: './home.component.html', - imports: [NgTemplateOutlet, LocalizationPipe, CardComponent, CardBodyComponent, ButtonComponent], + imports: [ + NgTemplateOutlet, + LocalizationPipe, + CardComponent, + CardBodyComponent, + ButtonComponent, + RouterLink + ], }) export class HomeComponent { protected readonly authService = inject(AuthService); - loading = false; + get hasLoggedIn(): boolean { return this.authService.isAuthenticated; } diff --git a/npm/ng-packs/apps/dev-app/src/assets/form-config.json b/npm/ng-packs/apps/dev-app/src/assets/form-config.json new file mode 100644 index 0000000000..d6876b9e96 --- /dev/null +++ b/npm/ng-packs/apps/dev-app/src/assets/form-config.json @@ -0,0 +1,73 @@ +[ + { + "key": "firstName", + "type": "text", + "label": "First Name", + "placeholder": "Enter first name", + "value": "erdemc", + "required": true, + "validators": [ + { "type": "required", "message": "First name is required" }, + { "type": "minLength", "value": 2, "message": "Minimum 2 characters required" } + ], + "gridSize": 6, + "order": 1 + }, + { + "key": "lastName", + "type": "text", + "label": "Last Name", + "placeholder": "Enter last name", + "required": true, + "validators": [{ "type": "required", "message": "Last name is required" }], + "gridSize": 12, + "order": 3 + }, + { + "key": "email", + "type": "email", + "label": "AbpAccount::EmailAddress", + "placeholder": "Enter email", + "required": true, + "validators": [ + { "type": "required", "message": "Email is required" }, + { "type": "email", "message": "Please enter a valid email" } + ], + "gridSize": 6, + "order": 2 + }, + { + "key": "userType", + "type": "select", + "label": "User Type", + "required": true, + "options": [ + { "key": "admin", "value": "Administrator" }, + { "key": "user", "value": "Regular User" }, + { "key": "guest", "value": "Guest User" } + ], + "validators": [{ "type": "required", "message": "Please select user type" }], + "order": 4 + }, + { + "key": "adminNotes", + "type": "textarea", + "label": "Admin Notes", + "placeholder": "Enter admin-specific notes", + "conditionalLogic": [ + { + "dependsOn": "userType", + "condition": "equals", + "value": "admin", + "action": "show" + } + ], + "order": 5 + }, + { + "key": "isSelected", + "type": "checkbox", + "label": "Is Selected", + "order": 6 + } +] diff --git a/npm/ng-packs/apps/dev-app/src/server.ts b/npm/ng-packs/apps/dev-app/src/server.ts index a8d7558341..fb45c1dec0 100644 --- a/npm/ng-packs/apps/dev-app/src/server.ts +++ b/npm/ng-packs/apps/dev-app/src/server.ts @@ -11,9 +11,7 @@ import {environment} from './environments/environment'; import * as oidc from 'openid-client'; import { ServerCookieParser } from '@abp/ng.core'; -if (environment.production === false) { - process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = "0"; -} +process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = "0"; const serverDistFolder = dirname(fileURLToPath(import.meta.url)); const browserDistFolder = resolve(serverDistFolder, '../browser'); diff --git a/npm/ng-packs/jest.config.ts b/npm/ng-packs/jest.config.ts index 6b3f2d6e24..2aa4dd9d8a 100644 --- a/npm/ng-packs/jest.config.ts +++ b/npm/ng-packs/jest.config.ts @@ -1,5 +1,8 @@ import { getJestProjectsAsync } from '@nx/jest'; - +/** + * @deprecated use vitest instead of jest + * @see https://vitest.dev/guide/migration.html#jest + */ export default async () => ({ projects: await getJestProjectsAsync(), }); diff --git a/npm/ng-packs/jest.preset.js b/npm/ng-packs/jest.preset.js index c1c3c4cdcd..861f91ccc4 100644 --- a/npm/ng-packs/jest.preset.js +++ b/npm/ng-packs/jest.preset.js @@ -1,3 +1,7 @@ +/** + * @deprecated use vitest instead of jest + * @see https://vitest.dev/guide/migration.html#jest + */ const nxPreset = require('@nx/jest/preset').default; module.exports = { diff --git a/npm/ng-packs/nx.json b/npm/ng-packs/nx.json index 500c939ad2..a573c5c59a 100644 --- a/npm/ng-packs/nx.json +++ b/npm/ng-packs/nx.json @@ -118,6 +118,10 @@ "cache": true, "dependsOn": ["^build"], "inputs": ["production", "^production"] + }, + "@nx/vitest:test": { + "cache": true, + "inputs": ["default", "^production"] } }, "namedInputs": { diff --git a/npm/ng-packs/package.json b/npm/ng-packs/package.json index e01812626e..fc80dbf797 100644 --- a/npm/ng-packs/package.json +++ b/npm/ng-packs/package.json @@ -10,6 +10,8 @@ "build:all": "nx run-many --target=build --all --exclude=dev-app,schematics --prod && npm run build:schematics", "test": "ng test --detect-open-handles=true --run-in-band=true --watch-all=true", "test:all": "nx run-many --target=test --all", + "test:vitest": "vitest", + "test:vitest:project": "sh -c 'vitest --project \"${1:-core}\"' _", "lint-staged": "lint-staged", "lint": "nx workspace-lint && ng lint", "lint:all": "nx run-many --target=lint --all", @@ -46,8 +48,8 @@ }, "private": true, "devDependencies": { - "@abp/ng.theme.lepton-x": "~5.1.0-rc.1", - "@abp/utils": "~10.1.0-rc.1", + "@abp/ng.theme.lepton-x": "~5.1.0-rc.2", + "@abp/utils": "~10.1.0-rc.2", "@angular-devkit/build-angular": "~21.0.0", "@angular-devkit/core": "~21.0.0", "@angular-devkit/schematics": "~21.0.0", @@ -56,7 +58,7 @@ "@angular-eslint/eslint-plugin-template": "~21.0.0", "@angular-eslint/template-parser": "~21.0.0", "@angular/animations": "21.0.0", - "@angular/aria": "~21.0.0", + "@angular/aria": "21.0.0", "@angular/build": "~21.0.0", "@angular/cli": "~21.0.0", "@angular/common": "~21.0.0", @@ -81,23 +83,26 @@ "@nx/eslint": "~22.2.0", "@nx/eslint-plugin": "~22.2.0", "@nx/jest": "~22.2.0", - "@nx/js": "~22.2.0", + "@nx/js": "22.2.7", "@nx/plugin": "~22.2.0", + "@nx/vite": "22.2.7", + "@nx/vitest": "22.2.7", "@nx/web": "~22.2.0", "@nx/workspace": "~22.2.0", "@popperjs/core": "~2.11.0", "@schematics/angular": "~21.0.0", "@swc-node/register": "1.9.2", "@swc/cli": "0.6.0", - "@swc/core": "~1.5.0", - "@swc/helpers": "~0.5.0", + "@swc/core": "~1.5.7", + "@swc/helpers": "~0.5.11", "@swimlane/ngx-datatable": "~22.0.0", "@types/express": "~5.0.0", "@types/jest": "29.5.14", - "@types/node": "~20.11.0", + "@types/node": "20.19.9", "@typescript-eslint/eslint-plugin": "7.16.0", "@typescript-eslint/parser": "7.16.0", "@typescript-eslint/utils": "^7.16.0", + "@vitest/coverage-v8": "^4.0.0", "angular-oauth2-oidc": "~20.0.0", "autoprefixer": "^10.4.21", "bootstrap": "~5.0.0", @@ -115,6 +120,7 @@ "jest-canvas-mock": "^2.0.0", "jest-environment-jsdom": "^29.0.0", "jest-preset-angular": "14.6.0", + "jsdom": "~22.1.0", "jsonc-eslint-parser": "^2.0.0", "jsonc-parser": "^2.0.0", "just-clone": "^6.0.0", @@ -138,6 +144,8 @@ "tslib": "^2.3.0", "tslint": "~6.1.0", "typescript": "~5.9.0", + "vite": "^7.0.0", + "vitest": "^4.0.0", "zone.js": "~0.15.0" }, "lint-staged": { diff --git a/npm/ng-packs/packages/account-core/.eslintrc.json b/npm/ng-packs/packages/account-core/.eslintrc.json index 5e303d20bb..afa6cfec5f 100644 --- a/npm/ng-packs/packages/account-core/.eslintrc.json +++ b/npm/ng-packs/packages/account-core/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "ignorePatterns": ["!**/*", "**/vitest.config.*.timestamp*"], "overrides": [ { "files": ["*.ts"], diff --git a/npm/ng-packs/packages/account-core/jest.config.ts b/npm/ng-packs/packages/account-core/jest.config.ts index 7ef24776c3..f6aa97e8c1 100644 --- a/npm/ng-packs/packages/account-core/jest.config.ts +++ b/npm/ng-packs/packages/account-core/jest.config.ts @@ -1,4 +1,8 @@ /* eslint-disable */ +/** + * @deprecated use vitest instead of jest + * @see https://vitest.dev/guide/migration.html#jest + */ export default { displayName: 'account-core', preset: '../../jest.preset.js', diff --git a/npm/ng-packs/packages/account-core/package.json b/npm/ng-packs/packages/account-core/package.json index 56b3340a19..48749859c2 100644 --- a/npm/ng-packs/packages/account-core/package.json +++ b/npm/ng-packs/packages/account-core/package.json @@ -1,14 +1,14 @@ { "name": "@abp/ng.account.core", - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.core": "~10.1.0-rc.1", - "@abp/ng.theme.shared": "~10.1.0-rc.1", + "@abp/ng.core": "~10.1.0-rc.2", + "@abp/ng.theme.shared": "~10.1.0-rc.2", "tslib": "^2.0.0" }, "publishConfig": { diff --git a/npm/ng-packs/packages/account-core/project.json b/npm/ng-packs/packages/account-core/project.json index 312bdd30e9..8a201b8006 100644 --- a/npm/ng-packs/packages/account-core/project.json +++ b/npm/ng-packs/packages/account-core/project.json @@ -23,16 +23,16 @@ }, "defaultConfiguration": "production" }, - "test": { - "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/packages/account-core"], - "options": { - "jestConfig": "packages/account-core/jest.config.ts" - } - }, "lint": { "executor": "@nx/eslint:lint", "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/vitest:test", + "outputs": ["{options.reportsDirectory}"], + "options": { + "reportsDirectory": "../../coverage/packages/account-core" + } } } } diff --git a/npm/ng-packs/packages/account-core/tsconfig.lib.json b/npm/ng-packs/packages/account-core/tsconfig.lib.json index 22d2695db8..80ebc37002 100644 --- a/npm/ng-packs/packages/account-core/tsconfig.lib.json +++ b/npm/ng-packs/packages/account-core/tsconfig.lib.json @@ -10,6 +10,23 @@ "lib": ["dom", "es2020"], "useDefineForClassFields": false }, - "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], + "exclude": [ + "src/test-setup.ts", + "**/*.spec.ts", + "jest.config.ts", + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/test-setup.ts" + ], "include": ["**/*.ts"] } diff --git a/npm/ng-packs/packages/account-core/tsconfig.spec.json b/npm/ng-packs/packages/account-core/tsconfig.spec.json index 023d7d0b51..fc61345bb3 100644 --- a/npm/ng-packs/packages/account-core/tsconfig.spec.json +++ b/npm/ng-packs/packages/account-core/tsconfig.spec.json @@ -2,10 +2,22 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", - "types": ["jest", "node"], - "esModuleInterop": true + "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node", "vitest"] }, - "files": ["src/test-setup.ts"], - "include": ["**/*.spec.ts", "**/*.d.ts", "jest.config.ts"] + "include": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "files": ["src/test-setup.ts"] } diff --git a/npm/ng-packs/packages/account-core/vitest.config.mts b/npm/ng-packs/packages/account-core/vitest.config.mts new file mode 100644 index 0000000000..0ef8748fc2 --- /dev/null +++ b/npm/ng-packs/packages/account-core/vitest.config.mts @@ -0,0 +1,21 @@ +import { defineConfig } from 'vitest/config'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/account-core', + plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + test: { + name: 'account-core', + watch: false, + globals: true, + environment: 'jsdom', + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/packages/account-core', + provider: 'v8' as const, + }, + }, +})); diff --git a/npm/ng-packs/packages/account/.eslintrc.json b/npm/ng-packs/packages/account/.eslintrc.json index 5e303d20bb..afa6cfec5f 100644 --- a/npm/ng-packs/packages/account/.eslintrc.json +++ b/npm/ng-packs/packages/account/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "ignorePatterns": ["!**/*", "**/vitest.config.*.timestamp*"], "overrides": [ { "files": ["*.ts"], diff --git a/npm/ng-packs/packages/account/jest.config.ts b/npm/ng-packs/packages/account/jest.config.ts index 7630a18eb3..207a21558c 100644 --- a/npm/ng-packs/packages/account/jest.config.ts +++ b/npm/ng-packs/packages/account/jest.config.ts @@ -1,4 +1,8 @@ /* eslint-disable */ +/** + * @deprecated use vitest instead of jest + * @see https://vitest.dev/guide/migration.html#jest + */ export default { displayName: 'account', preset: '../../jest.preset.js', diff --git a/npm/ng-packs/packages/account/package.json b/npm/ng-packs/packages/account/package.json index 41903ae20c..8e667e60ee 100644 --- a/npm/ng-packs/packages/account/package.json +++ b/npm/ng-packs/packages/account/package.json @@ -1,14 +1,14 @@ { "name": "@abp/ng.account", - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.account.core": "~10.1.0-rc.1", - "@abp/ng.theme.shared": "~10.1.0-rc.1", + "@abp/ng.account.core": "~10.1.0-rc.2", + "@abp/ng.theme.shared": "~10.1.0-rc.2", "tslib": "^2.0.0" }, "publishConfig": { diff --git a/npm/ng-packs/packages/account/project.json b/npm/ng-packs/packages/account/project.json index 41c6597e2a..5d02bebd76 100644 --- a/npm/ng-packs/packages/account/project.json +++ b/npm/ng-packs/packages/account/project.json @@ -23,16 +23,16 @@ }, "defaultConfiguration": "production" }, - "test": { - "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/packages/account"], - "options": { - "jestConfig": "packages/account/jest.config.ts" - } - }, "lint": { "executor": "@nx/eslint:lint", "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/vitest:test", + "outputs": ["{options.reportsDirectory}"], + "options": { + "reportsDirectory": "../../coverage/packages/account" + } } } } diff --git a/npm/ng-packs/packages/account/tsconfig.lib.json b/npm/ng-packs/packages/account/tsconfig.lib.json index 22d2695db8..80ebc37002 100644 --- a/npm/ng-packs/packages/account/tsconfig.lib.json +++ b/npm/ng-packs/packages/account/tsconfig.lib.json @@ -10,6 +10,23 @@ "lib": ["dom", "es2020"], "useDefineForClassFields": false }, - "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], + "exclude": [ + "src/test-setup.ts", + "**/*.spec.ts", + "jest.config.ts", + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/test-setup.ts" + ], "include": ["**/*.ts"] } diff --git a/npm/ng-packs/packages/account/tsconfig.spec.json b/npm/ng-packs/packages/account/tsconfig.spec.json index 023d7d0b51..fc61345bb3 100644 --- a/npm/ng-packs/packages/account/tsconfig.spec.json +++ b/npm/ng-packs/packages/account/tsconfig.spec.json @@ -2,10 +2,22 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", - "types": ["jest", "node"], - "esModuleInterop": true + "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node", "vitest"] }, - "files": ["src/test-setup.ts"], - "include": ["**/*.spec.ts", "**/*.d.ts", "jest.config.ts"] + "include": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "files": ["src/test-setup.ts"] } diff --git a/npm/ng-packs/packages/account/vitest.config.mts b/npm/ng-packs/packages/account/vitest.config.mts new file mode 100644 index 0000000000..ed2d3c8b2c --- /dev/null +++ b/npm/ng-packs/packages/account/vitest.config.mts @@ -0,0 +1,21 @@ +import { defineConfig } from 'vitest/config'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/account', + plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + test: { + name: 'account', + watch: false, + globals: true, + environment: 'jsdom', + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/packages/account', + provider: 'v8' as const, + }, + }, +})); diff --git a/npm/ng-packs/packages/components/.eslintrc.json b/npm/ng-packs/packages/components/.eslintrc.json index 5e303d20bb..afa6cfec5f 100644 --- a/npm/ng-packs/packages/components/.eslintrc.json +++ b/npm/ng-packs/packages/components/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "ignorePatterns": ["!**/*", "**/vitest.config.*.timestamp*"], "overrides": [ { "files": ["*.ts"], diff --git a/npm/ng-packs/packages/components/dynamic-form/NESTED-FORMS.md b/npm/ng-packs/packages/components/dynamic-form/NESTED-FORMS.md new file mode 100644 index 0000000000..1e628035bb --- /dev/null +++ b/npm/ng-packs/packages/components/dynamic-form/NESTED-FORMS.md @@ -0,0 +1,338 @@ +# Nested Forms Guide + +## Overview + +Dynamic Form now supports **nested forms** with two new field types: +- **`group`** - Group related fields together +- **`array`** - Dynamic lists with add/remove functionality + +## Quick Start + +### 1. Group Type (Nested Fields) + +Group related fields together with visual hierarchy: + +```typescript +{ + key: 'address', + type: 'group', + label: 'Address Information', + gridSize: 12, + children: [ + { + key: 'street', + type: 'text', + label: 'Street', + gridSize: 8 + }, + { + key: 'city', + type: 'text', + label: 'City', + gridSize: 4 + }, + { + key: 'zipCode', + type: 'text', + label: 'ZIP Code', + gridSize: 6 + } + ] +} +``` + +**Output:** +```json +{ + "address": { + "street": "123 Main St", + "city": "New York", + "zipCode": "10001" + } +} +``` + +### 2. Array Type (Dynamic Lists) + +Create dynamic lists with add/remove buttons: + +```typescript +{ + key: 'phoneNumbers', + type: 'array', + label: 'Phone Numbers', + minItems: 1, + maxItems: 5, + gridSize: 12, + children: [ + { + key: 'type', + type: 'select', + label: 'Type', + gridSize: 4, + options: { + defaultValues: [ + { key: 'mobile', value: 'Mobile' }, + { key: 'home', value: 'Home' }, + { key: 'work', value: 'Work' } + ] + } + }, + { + key: 'number', + type: 'tel', + label: 'Number', + gridSize: 8 + } + ] +} +``` + +**Output:** +```json +{ + "phoneNumbers": [ + { "type": "mobile", "number": "555-1234" }, + { "type": "work", "number": "555-5678" } + ] +} +``` + +## Features + +### Array Features +- ✅ **Add Button** - Adds new item (respects maxItems) +- ✅ **Remove Button** - Removes item (respects minItems) +- ✅ **Item Counter** - Shows current count and limits +- ✅ **Item Labels** - "Phone Number #1", "Phone Number #2" +- ✅ **Min/Max Validation** - Buttons automatically disabled +- ✅ **Empty State** - Shows info message when no items + +### Group Features +- ✅ **Visual Hierarchy** - Border and background styling +- ✅ **Legend Label** - Fieldset with legend for accessibility +- ✅ **Grid Support** - All children support gridSize +- ✅ **Nested Groups** - Groups inside groups supported + +### Recursive Support +- ✅ **Array in Array** - Phone numbers can have sub-arrays +- ✅ **Group in Array** - Work experience can have grouped fields +- ✅ **Array in Group** - Address can have multiple phone numbers +- ✅ **Unlimited Nesting** - No depth limit + +## Advanced Examples + +### Complex Nested Structure + +```typescript +{ + key: 'workExperience', + type: 'array', + label: 'Work Experience', + minItems: 0, + maxItems: 10, + children: [ + { + key: 'company', + type: 'text', + label: 'Company Name', + gridSize: 6, + required: true + }, + { + key: 'position', + type: 'text', + label: 'Position', + gridSize: 6, + required: true + }, + { + key: 'dates', + type: 'group', // Nested group inside array + label: 'Employment Dates', + gridSize: 12, + children: [ + { + key: 'startDate', + type: 'date', + label: 'Start Date', + gridSize: 6 + }, + { + key: 'endDate', + type: 'date', + label: 'End Date', + gridSize: 6 + } + ] + }, + { + key: 'description', + type: 'textarea', + label: 'Description', + gridSize: 12 + } + ] +} +``` + +## API Reference + +### FormFieldConfig (Extended) + +```typescript +interface FormFieldConfig { + // ... existing properties + + // NEW: Nested form properties + children?: FormFieldConfig[]; // Child fields for group/array types + minItems?: number; // Minimum items for array (default: 0) + maxItems?: number; // Maximum items for array (default: unlimited) +} +``` + +### New Components + +#### DynamicFormGroupComponent +```typescript +@Input() groupConfig: FormFieldConfig; +@Input() formGroup: FormGroup; +@Input() visible: boolean = true; +``` + +#### DynamicFormArrayComponent +```typescript +@Input() arrayConfig: FormFieldConfig; +@Input() formGroup: FormGroup; +@Input() visible: boolean = true; + +addItem(): void; // Add new item to array +removeItem(index): void; // Remove item from array +``` + +## Styling + +### Group Styling + +```scss +.form-group-container { + border-left: 3px solid var(--bs-primary); + padding: 1rem; + background-color: var(--bs-light); +} +``` + +### Array Styling + +```scss +.array-item { + border: 1px solid var(--bs-border-color); + padding: 1rem; + margin-bottom: 1rem; + background: white; + + &:hover { + box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); + } +} +``` + +## Accessibility + +All nested forms include: +- ✅ **ARIA roles** (`role="group"`, `role="list"`, `role="listitem"`) +- ✅ **ARIA labels** (`aria-label`, `aria-labelledby`) +- ✅ **Live regions** (`aria-live="polite"` for item count) +- ✅ **Semantic HTML** (`
`, ``) +- ✅ **Keyboard navigation** (Tab, Enter, Space) +- ✅ **Screen reader announcements** + +## Migration Guide + +### From Simple to Nested + +**Before:** +```typescript +{ + key: 'street', + type: 'text', + label: 'Street' +}, +{ + key: 'city', + type: 'text', + label: 'City' +} +``` + +**After:** +```typescript +{ + key: 'address', + type: 'group', + label: 'Address', + children: [ + { key: 'street', type: 'text', label: 'Street' }, + { key: 'city', type: 'text', label: 'City' } + ] +} +``` + +### Data Structure Change + +**Before:** +```json +{ + "street": "123 Main St", + "city": "New York" +} +``` + +**After:** +```json +{ + "address": { + "street": "123 Main St", + "city": "New York" + } +} +``` + +## Best Practices + +1. **Use Groups** for logical field grouping (address, contact info) +2. **Use Arrays** for dynamic lists (phone numbers, work history) +3. **Set minItems/maxItems** to prevent empty or excessive arrays +4. **Use gridSize** for responsive layouts within nested forms +5. **Keep nesting shallow** (max 2-3 levels for UX) +6. **Add validation** to required nested fields +7. **Use meaningful labels** for array items + +## Examples + +See `apps/dev-app/src/app/dynamic-form-page` for complete examples: +- Phone Numbers (simple array) +- Work Experience (complex array) +- Address (group) + +## Troubleshooting + +### Array items not showing +- Check `minItems` - may need to be > 0 +- Verify `children` array is not empty + +### Can't add items +- Check `maxItems` limit +- Verify button is not disabled + +### Form data not nested +- Confirm `type: 'group'` or `type: 'array'` +- Check FormGroup structure in component + +## Performance + +- ✅ **OnPush** change detection +- ✅ **TrackBy** functions for arrays +- ✅ **Lazy rendering** for conditional fields +- ✅ **Minimal re-renders** on add/remove + diff --git a/npm/ng-packs/packages/components/dynamic-form/ng-package.json b/npm/ng-packs/packages/components/dynamic-form/ng-package.json new file mode 100644 index 0000000000..e09fb3fd03 --- /dev/null +++ b/npm/ng-packs/packages/components/dynamic-form/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-entrypoint.schema.json", + "lib": { + "entryFile": "src/public-api.ts" + } +} diff --git a/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-array/dynamic-form-array.component.html b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-array/dynamic-form-array.component.html new file mode 100644 index 0000000000..2608180d15 --- /dev/null +++ b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-array/dynamic-form-array.component.html @@ -0,0 +1,93 @@ +@if (visible()) { +
+ + +
+ + +
+ + +
+ @for (item of formArray.controls; track trackByIndex($index)) { +
+ + +
+ + {{ arrayConfig().label | abpLocalization }} #{{ $index + 1 }} + + +
+ + +
+ @for (field of sortedChildren; track field.key) { +
+ + + @if (field.type === 'group') { + + } + + + @else if (field.type === 'array') { + + } + + + @else { + + } + +
+ } +
+
+ } @empty { +
+ + {{ '::NoItemsAdded' | abpLocalization }} +
+ } +
+ + + +
+} diff --git a/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-array/dynamic-form-array.component.scss b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-array/dynamic-form-array.component.scss new file mode 100644 index 0000000000..71707783e8 --- /dev/null +++ b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-array/dynamic-form-array.component.scss @@ -0,0 +1,75 @@ +.form-array-container { + margin-bottom: 1.5rem; +} + +.array-header { + border-bottom: 2px solid var(--bs-primary, #007bff); + padding-bottom: 0.5rem; +} + +.form-array-label { + font-size: 1.1rem; + font-weight: 600; + color: var(--bs-dark, #212529); + margin-bottom: 0; +} + +.array-items { + margin-top: 1rem; +} + +.array-item { + background-color: var(--bs-white, #fff); + transition: all 0.2s ease; + position: relative; + + &:hover { + box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); + transform: translateY(-1px); + } + + // Nested arrays get lighter background + .array-item { + background-color: var(--bs-light, #f8f9fa); + } +} + +.item-header { + border-bottom: 1px solid var(--bs-border-color, #dee2e6); + padding-bottom: 0.75rem; +} + +.item-title { + color: var(--bs-primary, #007bff); + font-size: 0.95rem; +} + +.array-footer { + margin-top: 0.5rem; + padding-top: 0.5rem; + border-top: 1px solid var(--bs-border-color, #dee2e6); +} + +// Accessibility: Focus styles for buttons +button { + &:focus-visible { + outline: 2px solid var(--bs-primary, #007bff); + outline-offset: 2px; + } +} + +// Animation for add/remove +@keyframes slideIn { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.array-item { + animation: slideIn 0.3s ease; +} diff --git a/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-array/dynamic-form-array.component.ts b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-array/dynamic-form-array.component.ts new file mode 100644 index 0000000000..eb86a9da7f --- /dev/null +++ b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-array/dynamic-form-array.component.ts @@ -0,0 +1,88 @@ +import { + ChangeDetectionStrategy, + Component, + input, + inject, + ChangeDetectorRef, + forwardRef, +} from '@angular/core'; +import { FormGroup, FormArray, FormBuilder, ReactiveFormsModule } from '@angular/forms'; +import { CommonModule } from '@angular/common'; +import { FormFieldConfig } from '../dynamic-form.models'; +import { DynamicFormService } from '../dynamic-form.service'; +import { LocalizationPipe } from '@abp/ng.core'; +import { DynamicFormFieldComponent } from '../dynamic-form-field'; +import { DynamicFormGroupComponent } from '../dynamic-form-group'; + +@Component({ + selector: 'abp-dynamic-form-array', + templateUrl: './dynamic-form-array.component.html', + styleUrls: ['./dynamic-form-array.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ + CommonModule, + ReactiveFormsModule, + LocalizationPipe, + DynamicFormFieldComponent, + DynamicFormGroupComponent, + forwardRef(() => DynamicFormArrayComponent), // Self reference for recursion + ], +}) +export class DynamicFormArrayComponent { + arrayConfig = input.required(); + formGroup = input.required(); + visible = input(true); + + private fb = inject(FormBuilder); + private dynamicFormService = inject(DynamicFormService); + private cdr = inject(ChangeDetectorRef); + + get formArray(): FormArray { + return this.formGroup().get(this.arrayConfig().key) as FormArray; + } + + get sortedChildren(): FormFieldConfig[] { + const children = this.arrayConfig().children || []; + return children.sort((a, b) => (a.order || 0) - (b.order || 0)); + } + + get canAddItem(): boolean { + const maxItems = this.arrayConfig().maxItems; + return maxItems ? this.formArray.length < maxItems : true; + } + + get canRemoveItem(): boolean { + const minItems = this.arrayConfig().minItems || 0; + return this.formArray.length > minItems; + } + + addItem() { + if (!this.canAddItem) return; + + const itemGroup = this.dynamicFormService.createFormGroup( + this.arrayConfig().children || [] + ); + + this.formArray.push(itemGroup); + this.cdr.markForCheck(); + } + + removeItem(index: number) { + if (!this.canRemoveItem) return; + + this.formArray.removeAt(index); + this.cdr.markForCheck(); + } + + getItemFormGroup(index: number): FormGroup { + return this.formArray.at(index) as FormGroup; + } + + getNestedFormGroup(index: number, key: string): FormGroup { + return this.getItemFormGroup(index).get(key) as FormGroup; + } + + trackByIndex(index: number): number { + return index; + } +} diff --git a/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-array/index.ts b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-array/index.ts new file mode 100644 index 0000000000..2ea9bc1460 --- /dev/null +++ b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-array/index.ts @@ -0,0 +1 @@ +export * from './dynamic-form-array.component'; diff --git a/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/dynamic-form-field-host.component.ts b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/dynamic-form-field-host.component.ts new file mode 100644 index 0000000000..56f32f6e0d --- /dev/null +++ b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/dynamic-form-field-host.component.ts @@ -0,0 +1,135 @@ +import { + Component, + ViewChild, + ViewContainerRef, + ChangeDetectionStrategy, + forwardRef, + Type, + effect, + DestroyRef, + inject, + input, +} from '@angular/core'; +import { + ControlValueAccessor, NG_VALUE_ACCESSOR, FormControl, ReactiveFormsModule +} from '@angular/forms'; +import { CommonModule } from '@angular/common'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; + +type controlValueAccessorLike = Partial & { setDisabledState?(d: boolean): void }; +type acceptsFormControl = { formControl?: FormControl }; + +@Component({ + selector: 'abp-dynamic-form-field-host', + imports: [CommonModule, ReactiveFormsModule], + template: ``, + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DynamicFieldHostComponent), + multi: true + }] +}) +export class DynamicFieldHostComponent implements ControlValueAccessor { + component = input>(); + inputs = input>({}); + + @ViewChild('vcRef', { read: ViewContainerRef, static: true }) viewContainerRef!: ViewContainerRef; + private componentRef?: any; + + private value: any; + private disabled = false; + + // if child has not implemented ControlValueAccessor. Create form control + private innerControl = new FormControl(null); + readonly destroyRef = inject(DestroyRef); + + constructor() { + effect(() => { + if (this.component()) { + this.createChild(); + } else if (this.componentRef && this.inputs()) { + this.applyInputs(); + } + }); + } + + private createChild() { + this.viewContainerRef.clear(); + if (!this.component()) return; + + this.componentRef = this.viewContainerRef.createComponent(this.component()); + this.applyInputs(); + + const instance: any = this.componentRef.instance as controlValueAccessorLike & acceptsFormControl; + + if (this.isCVA(instance)) { + // Child CVA ise wrapper -> child delege + instance.registerOnChange?.((v: any) => this.onChange(v)); + instance.registerOnTouched?.(() => this.onTouched()); + if (this.disabled && instance.setDisabledState) { + instance.setDisabledState(true); + } + // set initial value + if (this.value !== undefined) { + instance.writeValue?.(this.value); + } + } else { + // No CVA -> use form control + if ('formControl' in instance) { + instance.formControl = this.innerControl; + // apply initial value/disabled state + if (this.value !== undefined) { + this.innerControl.setValue(this.value, { emitEvent: false }); + } + this.innerControl.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(v => this.onChange(v)); + this.innerControl.disabled ? null : (this.disabled && this.innerControl.disable({ emitEvent: false })); + } + } + } + + private applyInputs() { + if (!this.componentRef) return; + const inst = this.componentRef.instance; + for (const [k, v] of Object.entries(this.inputs ?? {})) { + inst[k] = v; + } + this.componentRef.changeDetectorRef?.markForCheck?.(); + } + + private isCVA(obj: any): obj is controlValueAccessorLike { + return obj && typeof obj.writeValue === 'function' && typeof obj.registerOnChange === 'function'; + } + + writeValue(obj: any): void { + this.value = obj; + if (!this.componentRef) return; + + const inst: any = this.componentRef.instance as controlValueAccessorLike & acceptsFormControl; + + if (this.isCVA(inst)) { + inst.writeValue?.(obj); + } else if ('formControl' in inst && inst.formControl instanceof FormControl) { + inst.formControl.setValue(obj, { emitEvent: false }); + } + } + + private onChange: (v: any) => void = () => {}; + private onTouched: () => void = () => {}; + + registerOnChange(fn: any): void { this.onChange = fn; } + registerOnTouched(fn: any): void { this.onTouched = fn; } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (!this.componentRef) return; + + const inst = this.componentRef.instance as controlValueAccessorLike & acceptsFormControl; + + if (this.isCVA(inst) && inst.setDisabledState) { + inst.setDisabledState(isDisabled); + } else if ('formControl' in inst && inst.formControl instanceof FormControl) { + isDisabled ? inst.formControl.disable({ emitEvent: false }) : inst.formControl.enable({ emitEvent: false }); + } + } +} diff --git a/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/dynamic-form-field.component.html b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/dynamic-form-field.component.html new file mode 100644 index 0000000000..ed33dca227 --- /dev/null +++ b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/dynamic-form-field.component.html @@ -0,0 +1,371 @@ +@if (visible()) { +
+ + + + + @if (field().type === 'text') { + +
+ + + @if (isInvalid) { + + } +
+ } @else if (field().type === 'select') { + +
+ + + @if (isInvalid) { + + } +
+ } @else if (field().type === 'checkbox') { + +
+ + @if (isInvalid) { + + } +
+ } @else if (field().type === 'email') { + +
+ + + @if (isInvalid) { + + } +
+ } @else if (field().type === 'textarea') { + +
+ + + @if (isInvalid) { + + } +
+ } @else if (field().type === 'number') { + +
+ + + @if (isInvalid) { + + } +
+ } @else if (field().type === 'date') { + +
+ + + @if (isInvalid) { + + } +
+ } @else if (field().type === 'datetime-local') { + +
+ + + @if (isInvalid) { + + } +
+ } @else if (field().type === 'time') { + +
+ + + @if (isInvalid) { + + } +
+ } @else if (field().type === 'password') { + +
+ + + @if (isInvalid) { + + } +
+ } @else if (field().type === 'tel') { + +
+ + + @if (isInvalid) { + + } +
+ } @else if (field().type === 'url') { + +
+ + + @if (isInvalid) { + + } +
+ } @else if (field().type === 'radio') { + +
+ +
+ @for (option of options$ | async; track option.key) { +
+ + +
+ } +
+ @if (isInvalid) { + + } +
+ } @else if (field().type === 'file') { + +
+ + + @if (isInvalid) { + + } +
+ } @else if (field().type === 'range') { + +
+ +
+ + {{ value.value }} +
+ @if (isInvalid) { + + } +
+ } @else if (field().type === 'color') { + +
+ +
+ + {{ value.value || '#000000' }} +
+ @if (isInvalid) { + + } +
+ } +
+} + + + + + + + + \ No newline at end of file diff --git a/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/dynamic-form-field.component.scss b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/dynamic-form-field.component.scss new file mode 100644 index 0000000000..12870a4141 --- /dev/null +++ b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/dynamic-form-field.component.scss @@ -0,0 +1,12 @@ +// Minimal styling - rely on Bootstrap/Lepton-X theme styles +.form-group { + display: flex; + flex-direction: column; + + // Radio group spacing (layout only) + .radio-group { + display: flex; + flex-direction: column; + gap: 0.5rem; + } +} diff --git a/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/dynamic-form-field.component.ts b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/dynamic-form-field.component.ts new file mode 100644 index 0000000000..5302d5cdb9 --- /dev/null +++ b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/dynamic-form-field.component.ts @@ -0,0 +1,180 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + DestroyRef, + forwardRef, + inject, + InjectionToken, Injector, + input, + OnInit, +} from '@angular/core'; +import { FormFieldConfig } from '../dynamic-form.models'; +import { + ControlValueAccessor, + FormBuilder, + FormControl, + FormControlName, + FormGroupDirective, + NG_VALUE_ACCESSOR, + NgControl, + FormGroup, + ReactiveFormsModule, +} from '@angular/forms'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { NgTemplateOutlet, AsyncPipe } from '@angular/common'; +import { LocalizationPipe } from '@abp/ng.core'; +import { FormCheckboxComponent } from '@abp/ng.theme.shared'; +import { Observable, of } from 'rxjs'; +import { DynamicFormService } from '../dynamic-form.service'; + +export const ABP_DYNAMIC_FORM_FIELD = new InjectionToken('AbpDynamicFormField'); + +const DYNAMIC_FORM_FIELD_CONTROL_VALUE_ACCESSOR = { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DynamicFormFieldComponent), + multi: true, +}; + +@Component({ + selector: 'abp-dynamic-form-field', + templateUrl: './dynamic-form-field.component.html', + styleUrls: ['./dynamic-form-field.component.scss'], + providers: [ + { provide: ABP_DYNAMIC_FORM_FIELD, useExisting: DynamicFormFieldComponent }, + DYNAMIC_FORM_FIELD_CONTROL_VALUE_ACCESSOR, + ], + host: { class: 'abp-dynamic-form-field' }, + exportAs: 'abpDynamicFormField', + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [NgTemplateOutlet, LocalizationPipe, ReactiveFormsModule, FormCheckboxComponent, AsyncPipe], +}) +export class DynamicFormFieldComponent implements OnInit, ControlValueAccessor { + field = input.required(); + visible = input(true); + control!: FormControl; + fieldFormGroup: FormGroup; + readonly changeDetectorRef = inject(ChangeDetectorRef); + readonly destroyRef = inject(DestroyRef); + private injector = inject(Injector); + private formBuilder = inject(FormBuilder); + private dynamicFormService = inject(DynamicFormService); + + options$: Observable<{ key: string; value: any }[]> = of([]); + + // Accessibility: Generate unique IDs for ARIA + get fieldId(): string { + return `field-${this.field().key}`; + } + + get errorId(): string { + return `${this.fieldId}-error`; + } + + get helpTextId(): string { + return `${this.fieldId}-help`; + } + + constructor() { + this.fieldFormGroup = this.formBuilder.group({ + value: [{ value: '' }], + }); + } + + ngOnInit() { + const ngControl = this.injector.get(NgControl, null); + if (ngControl) { + this.control = this.injector.get(FormGroupDirective).getControl(ngControl as FormControlName); + } + this.value.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(value => { + this.onChange(value); + }); + + const options = this.field().options; + + if (options?.url) { + this.options$ = this.dynamicFormService.getOptions(options.url, options.apiName); + } else if (options?.defaultValues?.length) { + this.options$ = of( + options.defaultValues.map(item => { + return { + key: item[options.valueProp || 'key'] || item, + value: item[options.labelProp || 'value'] || item + }; + }) + ); + } else { + this.options$ = of([]); + } + } + + writeValue(value: any[]): void { + this.value.setValue(value || ''); + this.changeDetectorRef.markForCheck(); + } + + registerOnChange(fn: any): void { + this.onChange = fn; + } + + registerOnTouched(fn: any): void { + this.onTouched = fn; + } + + setDisabledState(isDisabled: boolean): void { + if (isDisabled) { + this.value.disable(); + } else { + this.value.enable(); + } + this.changeDetectorRef.markForCheck(); + } + + get isInvalid(): boolean { + if (this.control) { + return this.control.invalid && (this.control.dirty || this.control.touched); + } + return false; + } + + get errors(): string[] { + if (!this.control?.errors) return []; + if (this.control && this.control.errors) { + const errorKeys = Object.keys(this.control.errors); + const validators = this.field().validators || []; + return errorKeys.map(key => { + const validator = validators.find( + v => v.type.toLowerCase() === key.toLowerCase(), + ); + if (validator && validator.message) { + return validator.message; + } + // Fallback error messages + if (key === 'required') return `${this.field().label} is required`; + if (key === 'email') return 'Please enter a valid email address'; + if (key === 'minlength') + return `Minimum length is ${this.control.errors[key].requiredLength}`; + if (key === 'maxlength') + return `Maximum length is ${this.control.errors[key].requiredLength}`; + return `${this.field().label} is invalid due to ${key} validation.`; + }); + } + return []; + } + get value() { + return this.fieldFormGroup.get('value'); + } + + onFileChange(event: Event) { + const input = event.target as HTMLInputElement; + if (input.files) { + const files = Array.from(input.files); + const value = this.field().multiple ? files : files[0]; + this.value.setValue(value); + this.onChange(value); + } + } + + private onChange: (value: any) => void = () => { }; + private onTouched: () => void = () => { }; +} diff --git a/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/index.ts b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/index.ts new file mode 100644 index 0000000000..826f7c70d2 --- /dev/null +++ b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-field/index.ts @@ -0,0 +1,2 @@ +export * from './dynamic-form-field.component'; +export * from './dynamic-form-field-host.component'; diff --git a/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-group/dynamic-form-group.component.html b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-group/dynamic-form-group.component.html new file mode 100644 index 0000000000..1222faa58a --- /dev/null +++ b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-group/dynamic-form-group.component.html @@ -0,0 +1,36 @@ +@if (visible()) { +
+ + {{ groupConfig().label | abpLocalization }} + + +
+ @for (field of sortedChildren; track field.key) { +
+ + + @if (field.type === 'group') { + + } + + + @else if (field.type === 'array') { + + } + + + @else { + + } + +
+ } +
+
+} diff --git a/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-group/dynamic-form-group.component.scss b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-group/dynamic-form-group.component.scss new file mode 100644 index 0000000000..b98d92458c --- /dev/null +++ b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-group/dynamic-form-group.component.scss @@ -0,0 +1,26 @@ +.form-group-container { + border-left: 3px solid var(--bs-primary, #007bff); + padding-left: 1rem; + margin-bottom: 1.5rem; + border: 1px solid var(--bs-border-color, #dee2e6); + border-radius: 0.375rem; + padding: 1rem; + background-color: var(--bs-light, #f8f9fa); + + // Nested groups get lighter styling + .form-group-container { + border-left-color: var(--bs-secondary, #6c757d); + padding-left: 0.75rem; + background-color: var(--bs-white, #fff); + } +} + +.form-group-legend { + font-size: 1.1rem; + font-weight: 600; + color: var(--bs-primary, #007bff); + margin-bottom: 1rem; + padding: 0 0.5rem; + float: none; + width: auto; +} diff --git a/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-group/dynamic-form-group.component.ts b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-group/dynamic-form-group.component.ts new file mode 100644 index 0000000000..ab9cb0f3ad --- /dev/null +++ b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-group/dynamic-form-group.component.ts @@ -0,0 +1,45 @@ +import { + ChangeDetectionStrategy, + Component, + input, + forwardRef, +} from '@angular/core'; +import { FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { CommonModule } from '@angular/common'; +import { FormFieldConfig } from '../dynamic-form.models'; +import { LocalizationPipe } from '@abp/ng.core'; +import { DynamicFormFieldComponent } from '../dynamic-form-field'; +import { DynamicFormArrayComponent } from '../dynamic-form-array'; + +@Component({ + selector: 'abp-dynamic-form-group', + templateUrl: './dynamic-form-group.component.html', + styleUrls: ['./dynamic-form-group.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ + CommonModule, + ReactiveFormsModule, + LocalizationPipe, + DynamicFormFieldComponent, + forwardRef(() => DynamicFormArrayComponent), + forwardRef(() => DynamicFormGroupComponent), // Self reference for recursion + ], +}) +export class DynamicFormGroupComponent { + groupConfig = input.required(); + formGroup = input.required(); + visible = input(true); + + get sortedChildren(): FormFieldConfig[] { + const children = this.groupConfig().children || []; + return children.sort((a, b) => (a.order || 0) - (b.order || 0)); + } + + getChildFormGroup(key: string): FormGroup { + return this.formGroup().get(key) as FormGroup; + } + + getChildControl(key: string) { + return this.formGroup().get(key); + } +} diff --git a/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-group/index.ts b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-group/index.ts new file mode 100644 index 0000000000..899c3e295e --- /dev/null +++ b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form-group/index.ts @@ -0,0 +1 @@ +export * from './dynamic-form-group.component'; diff --git a/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form.component.html b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form.component.html new file mode 100644 index 0000000000..17bce17b1f --- /dev/null +++ b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form.component.html @@ -0,0 +1,78 @@ +
+
+
+ @for (field of sortedFields; track field.key) { +
+ + + @if (field.component) { + + + } + + + @else if (field.type === 'group') { + + + } + + + @else if (field.type === 'array') { + + + } + + + @else { + + + } + +
+ } +
+ + + + +
+ + +
+ @if (showCancelButton()) { + + } + +
+
+
diff --git a/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form.component.scss b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form.component.scss new file mode 100644 index 0000000000..038d8eed94 --- /dev/null +++ b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form.component.scss @@ -0,0 +1,15 @@ +:host(.abp-dynamic-form) { + form { + display: flex; + flex-direction: column; + gap: 0.5rem; + } + .form-wrapper { + text-align: left; + } +} +.form-actions { + display: flex; + justify-content: flex-end; + gap: 0.5rem; +} diff --git a/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form.component.ts b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form.component.ts new file mode 100644 index 0000000000..9e03cc0a2e --- /dev/null +++ b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form.component.ts @@ -0,0 +1,203 @@ +import { + ChangeDetectionStrategy, + Component, + input, + output, + inject, + OnInit, + DestroyRef, + ChangeDetectorRef, +} from '@angular/core'; +import { FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { CommonModule } from '@angular/common'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { DynamicFormService } from './dynamic-form.service'; +import { ConditionalAction, FormFieldConfig } from './dynamic-form.models'; +import { DynamicFormFieldComponent, DynamicFieldHostComponent } from './dynamic-form-field'; +import { DynamicFormGroupComponent } from './dynamic-form-group'; +import { DynamicFormArrayComponent } from './dynamic-form-array'; + +@Component({ + selector: 'abp-dynamic-form', + templateUrl: './dynamic-form.component.html', + styleUrls: ['./dynamic-form.component.scss'], + host: { class: 'abp-dynamic-form' }, + changeDetection: ChangeDetectionStrategy.OnPush, + exportAs: 'abpDynamicForm', + imports: [ + CommonModule, + DynamicFormFieldComponent, + DynamicFormGroupComponent, + DynamicFormArrayComponent, + ReactiveFormsModule, + DynamicFieldHostComponent, + ], +}) +export class DynamicFormComponent implements OnInit { + fields = input([]); + values = input>(); + submitButtonText = input('Submit'); + submitInProgress = input(false); + showCancelButton = input(false); + onSubmit = output(); + formCancel = output(); + private dynamicFormService = inject(DynamicFormService); + readonly destroyRef = inject(DestroyRef); + readonly changeDetectorRef = inject(ChangeDetectorRef); + + dynamicForm!: FormGroup; + fieldVisibility: { [key: string]: boolean } = {}; + + ngOnInit() { + this.setupFormAndLogic(); + } + + get sortedFields(): FormFieldConfig[] { + return this.fields().sort((a, b) => (a.order || 0) - (b.order || 0)); + } + + submit() { + if (this.dynamicForm.valid) { + this.onSubmit.emit(this.dynamicForm.getRawValue()); + } else { + this.markAllFieldsAsTouched(); + this.focusFirstInvalidField(); + } + } + + onCancel() { + this.formCancel.emit(); + } + + onFieldChange(event: { fieldKey: string; value: any }) { + this.evaluateConditionalLogic(event.fieldKey); + } + + isFieldVisible(field: FormFieldConfig): boolean { + return this.fieldVisibility[field.key] !== false; + } + + getChildFormGroup(key: string): FormGroup { + return this.dynamicForm.get(key) as FormGroup; + } + + resetForm() { + const initialValues: { [key: string]: any } = this.dynamicFormService.getInitialValues( + this.fields(), + ); + this.dynamicForm.reset({ ...initialValues }); + this.dynamicForm.markAsUntouched(); + this.dynamicForm.markAsPristine(); + this.changeDetectorRef.markForCheck(); + } + + private initializeFieldVisibility() { + this.fields().forEach(field => { + this.fieldVisibility = { + ...this.fieldVisibility, + [field.key]: !field.conditionalLogic?.length, + }; + }); + } + + private setupConditionalLogic() { + this.fields().forEach(field => { + if (field.conditionalLogic) { + field.conditionalLogic.forEach(rule => { + const dependentControl = this.dynamicForm.get(rule.dependsOn); + if (dependentControl) { + this.evaluateConditionalLogic(field.key); + dependentControl.valueChanges + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(() => { + this.evaluateConditionalLogic(field.key); + }); + } + }); + } + }); + } + + private evaluateConditionalLogic(fieldKey: string) { + const field = this.fields().find(f => f.key === fieldKey); + if (!field?.conditionalLogic) return; + + field.conditionalLogic.forEach(rule => { + const dependentValue = this.dynamicForm.get(rule.dependsOn)?.value; + const conditionMet = this.evaluateCondition(dependentValue, rule.condition, rule.value); + + this.applyConditionalAction(fieldKey, rule.action, conditionMet); + }); + } + + private evaluateCondition(fieldValue: any, condition: string, ruleValue: any): boolean { + switch (condition) { + case 'equals': + return fieldValue === ruleValue; + case 'notEquals': + return fieldValue !== ruleValue; + case 'contains': + return fieldValue && fieldValue.includes && fieldValue.includes(ruleValue); + case 'greaterThan': + return Number(fieldValue) > Number(ruleValue); + case 'lessThan': + return Number(fieldValue) < Number(ruleValue); + default: + return false; + } + } + + private applyConditionalAction(fieldKey: string, action: string, shouldApply: boolean) { + const control = this.dynamicForm.get(fieldKey); + + switch (action) { + case ConditionalAction.SHOW: + this.fieldVisibility = { ...this.fieldVisibility, [fieldKey]: shouldApply }; + break; + case ConditionalAction.HIDE: + this.fieldVisibility = { ...this.fieldVisibility, [fieldKey]: !shouldApply }; + break; + case ConditionalAction.ENABLE: + if (control) { + shouldApply ? control.enable() : control.disable(); + } + break; + case ConditionalAction.DISABLE: + if (control) { + shouldApply ? control.disable() : control.enable(); + } + break; + } + } + + private setupFormAndLogic() { + this.dynamicForm = this.dynamicFormService.createFormGroup(this.fields()); + this.initializeFieldVisibility(); + this.setupConditionalLogic(); + this.changeDetectorRef.markForCheck(); + } + + private markAllFieldsAsTouched() { + Object.keys(this.dynamicForm.controls).forEach(key => { + this.dynamicForm.get(key)?.markAsTouched(); + }); + } + + private focusFirstInvalidField() { + // Accessibility: Focus first invalid field for screen readers + const firstInvalidField = this.sortedFields.find(field => { + const control = this.dynamicForm.get(field.key); + return control && control.invalid && control.touched; + }); + + if (firstInvalidField) { + setTimeout(() => { + const element = document.getElementById(`field-${firstInvalidField.key}`); + if (element) { + element.focus(); + element.scrollIntoView({ behavior: 'smooth', block: 'center' }); + } + }, 100); + } + } +} diff --git a/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form.models.ts b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form.models.ts new file mode 100644 index 0000000000..864fc989f5 --- /dev/null +++ b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form.models.ts @@ -0,0 +1,60 @@ +import { Type } from '@angular/core'; +import { ControlValueAccessor } from '@angular/forms'; + +export interface FormFieldConfig { + key: string; + value?: any; + type: 'text' | 'email' | 'number' | 'select' | 'checkbox' | 'date' | 'textarea' | 'datetime-local' | 'time' | 'password' | 'tel' | 'url' | 'radio' | 'file' | 'range' | 'color' | 'group' | 'array'; + label: string; + placeholder?: string; + required?: boolean; + disabled?: boolean; + options?: OptionProps; + validators?: ValidatorConfig[]; + conditionalLogic?: ConditionalRule[]; + order?: number; + gridSize?: number; + component?: Type; + // Additional field attributes + min?: number | string; // For number, date, time, range + max?: number | string; // For number, date, time, range + step?: number | string; // For number, time, range + minLength?: number; // For text, password + maxLength?: number; // For text, password + pattern?: string; // For tel, text + accept?: string; // For file input (e.g., "image/*") + multiple?: boolean; // For file input + // Nested form support (for group and array types) + children?: FormFieldConfig[]; // Child fields for nested forms + minItems?: number; // For array type: minimum number of items + maxItems?: number; // For array type: maximum number of items +} + +export interface ValidatorConfig { + type: 'required' | 'email' | 'minLength' | 'maxLength' | 'pattern' | 'custom' | 'min' | 'max' | 'requiredTrue'; + value?: any; + message: string; +} + +export interface ConditionalRule { + dependsOn: string; + condition: 'equals' | 'notEquals' | 'contains' | 'greaterThan' | 'lessThan'; + value: any; + action: 'show' | 'hide' | 'enable' | 'disable'; +} + +export enum ConditionalAction { + SHOW = 'show', + HIDE = 'hide', + ENABLE = 'enable', + DISABLE = 'disable' +} + +export interface OptionProps { + defaultValues?: T[]; + url?: string; + disabled?: (option: T) => boolean; + labelProp?: string; + valueProp?: string; + apiName?: string; +} diff --git a/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form.service.ts b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form.service.ts new file mode 100644 index 0000000000..0dbc302c20 --- /dev/null +++ b/npm/ng-packs/packages/components/dynamic-form/src/dynamic-form.service.ts @@ -0,0 +1,119 @@ +import {Injectable, inject} from '@angular/core'; +import {FormControl, FormGroup, FormArray, ValidatorFn, Validators, FormBuilder} from '@angular/forms'; +import {FormFieldConfig, ValidatorConfig} from './dynamic-form.models'; +import { RestService } from '@abp/ng.core'; + +@Injectable({ + providedIn: 'root' +}) + +export class DynamicFormService { + + private formBuilder = inject(FormBuilder); + private restService = inject(RestService); + apiName = 'DynamicFormService'; + + createFormGroup(fields: FormFieldConfig[]): FormGroup { + const group: any = {}; + + fields.forEach(field => { + // Nested Group + if (field.type === 'group') { + group[field.key] = this.createFormGroup(field.children || []); + } + // Nested Array + else if (field.type === 'array') { + group[field.key] = this.createFormArray(field); + } + // Regular Field + else { + const validators = this.buildValidators(field.validators || []); + const initialValue = this.getInitialValue(field); + + group[field.key] = new FormControl({ + value: initialValue, + disabled: field.disabled || false + }, validators); + } + }); + + return this.formBuilder.group(group); + } + + createFormArray(arrayConfig: FormFieldConfig): FormArray { + const items: FormGroup[] = []; + const minItems = arrayConfig.minItems || 0; + + // Create minimum required items + for (let i = 0; i < minItems; i++) { + items.push(this.createFormGroup(arrayConfig.children || [])); + } + + return this.formBuilder.array(items); + } + + getInitialValues(fields: FormFieldConfig[]): any { + const initialValues: any = {}; + fields.forEach(field => { + if (field.type === 'group') { + initialValues[field.key] = this.getInitialValues(field.children || []); + } else if (field.type === 'array') { + initialValues[field.key] = []; + } else { + initialValues[field.key] = this.getInitialValue(field); + } + }); + return initialValues; + } + + getOptions(url: string, apiName?: string): any { + return this.restService.request({ + method: 'GET', + url, + }, + { apiName: apiName || this.apiName }); + } + + private buildValidators(validatorConfigs: ValidatorConfig[]): ValidatorFn[] { + return validatorConfigs.map(config => { + switch (config.type) { + case 'required': + return Validators.required; + case 'email': + return Validators.email; + case 'minLength': + return Validators.minLength(config.value); + case 'maxLength': + return Validators.maxLength(config.value); + case 'pattern': + return Validators.pattern(config.value); + case 'min': + return Validators.min(config.value); + case 'max': + return Validators.max(config.value); + case 'requiredTrue': + return Validators.requiredTrue; + default: + return Validators.nullValidator; + } + }); + } + + private getInitialValue(field: FormFieldConfig): any { + if (field.value !== undefined) { + return field.value; + } + switch (field.type) { + case 'checkbox': + return false; + case 'number': + return 0; + case 'group': + return this.getInitialValues(field.children || []); + case 'array': + return []; + default: + return ''; + } + } +} diff --git a/npm/ng-packs/packages/components/dynamic-form/src/public-api.ts b/npm/ng-packs/packages/components/dynamic-form/src/public-api.ts new file mode 100644 index 0000000000..f9dc670737 --- /dev/null +++ b/npm/ng-packs/packages/components/dynamic-form/src/public-api.ts @@ -0,0 +1,6 @@ +export * from './dynamic-form.component'; +export * from './dynamic-form-field'; +export * from './dynamic-form.models'; +export * from './dynamic-form.service'; +export * from './dynamic-form-group'; +export * from './dynamic-form-array'; diff --git a/npm/ng-packs/packages/components/jest.config.ts b/npm/ng-packs/packages/components/jest.config.ts index ff37b7e9e4..4a2a95bc72 100644 --- a/npm/ng-packs/packages/components/jest.config.ts +++ b/npm/ng-packs/packages/components/jest.config.ts @@ -1,4 +1,8 @@ /* eslint-disable */ +/** + * @deprecated use vitest instead of jest + * @see https://vitest.dev/guide/migration.html#jest + */ export default { displayName: 'components', preset: '../../jest.preset.js', diff --git a/npm/ng-packs/packages/components/package.json b/npm/ng-packs/packages/components/package.json index 6fdbd3d622..f991cb13d4 100644 --- a/npm/ng-packs/packages/components/package.json +++ b/npm/ng-packs/packages/components/package.json @@ -1,14 +1,14 @@ { "name": "@abp/ng.components", - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/abpframework/abp.git" }, "peerDependencies": { - "@abp/ng.core": ">=10.1.0-rc.1", - "@abp/ng.theme.shared": ">=10.1.0-rc.1" + "@abp/ng.core": ">=10.1.0-rc.2", + "@abp/ng.theme.shared": ">=10.1.0-rc.2" }, "dependencies": { "chart.js": "^3.5.1", diff --git a/npm/ng-packs/packages/components/project.json b/npm/ng-packs/packages/components/project.json index 671fabe461..d80db9b26d 100644 --- a/npm/ng-packs/packages/components/project.json +++ b/npm/ng-packs/packages/components/project.json @@ -23,16 +23,16 @@ }, "defaultConfiguration": "production" }, - "test": { - "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/packages/components"], - "options": { - "jestConfig": "packages/components/jest.config.ts" - } - }, "lint": { "executor": "@nx/eslint:lint", "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/vitest:test", + "outputs": ["{options.reportsDirectory}"], + "options": { + "reportsDirectory": "../../coverage/packages/components" + } } } } diff --git a/npm/ng-packs/packages/components/tsconfig.lib.json b/npm/ng-packs/packages/components/tsconfig.lib.json index 7dde5f04bf..e4e2e20714 100644 --- a/npm/ng-packs/packages/components/tsconfig.lib.json +++ b/npm/ng-packs/packages/components/tsconfig.lib.json @@ -13,7 +13,19 @@ "exclude": [ "src/test-setup.ts", "src/**/*.spec.ts", - "jest.config.ts" + "jest.config.ts", + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/test-setup.ts" ], "include": ["src/**/*.ts"] } diff --git a/npm/ng-packs/packages/components/tsconfig.spec.json b/npm/ng-packs/packages/components/tsconfig.spec.json index 023d7d0b51..fc61345bb3 100644 --- a/npm/ng-packs/packages/components/tsconfig.spec.json +++ b/npm/ng-packs/packages/components/tsconfig.spec.json @@ -2,10 +2,22 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", - "types": ["jest", "node"], - "esModuleInterop": true + "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node", "vitest"] }, - "files": ["src/test-setup.ts"], - "include": ["**/*.spec.ts", "**/*.d.ts", "jest.config.ts"] + "include": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "files": ["src/test-setup.ts"] } diff --git a/npm/ng-packs/packages/components/vitest.config.mts b/npm/ng-packs/packages/components/vitest.config.mts new file mode 100644 index 0000000000..2037268556 --- /dev/null +++ b/npm/ng-packs/packages/components/vitest.config.mts @@ -0,0 +1,21 @@ +import { defineConfig } from 'vitest/config'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/components', + plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + test: { + name: 'components', + watch: false, + globals: true, + environment: 'jsdom', + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/packages/components', + provider: 'v8' as const, + }, + }, +})); diff --git a/npm/ng-packs/packages/core/.eslintrc.json b/npm/ng-packs/packages/core/.eslintrc.json index 5e303d20bb..afa6cfec5f 100644 --- a/npm/ng-packs/packages/core/.eslintrc.json +++ b/npm/ng-packs/packages/core/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "ignorePatterns": ["!**/*", "**/vitest.config.*.timestamp*"], "overrides": [ { "files": ["*.ts"], diff --git a/npm/ng-packs/packages/core/jest.config.ts b/npm/ng-packs/packages/core/jest.config.ts deleted file mode 100644 index 3dda0ce769..0000000000 --- a/npm/ng-packs/packages/core/jest.config.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -export default { - displayName: 'core', - preset: '../../jest.preset.js', - setupFilesAfterEnv: ['/src/test-setup.ts'], - globals: {}, - coverageDirectory: '../../coverage/packages/core', - transform: { - '^.+.(ts|mjs|js|html)$': [ - 'jest-preset-angular', - { - tsconfig: '/tsconfig.spec.json', - stringifyContentPathRegex: '\\.(html|svg)$', - }, - ], - }, - transformIgnorePatterns: ['node_modules/(?!.*.mjs$)'], - snapshotSerializers: [ - 'jest-preset-angular/build/serializers/no-ng-attributes', - 'jest-preset-angular/build/serializers/ng-snapshot', - 'jest-preset-angular/build/serializers/html-comment', - ], -}; diff --git a/npm/ng-packs/packages/core/package.json b/npm/ng-packs/packages/core/package.json index 03ad0f5524..da1307759c 100644 --- a/npm/ng-packs/packages/core/package.json +++ b/npm/ng-packs/packages/core/package.json @@ -1,13 +1,13 @@ { "name": "@abp/ng.core", - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/utils": "~10.1.0-rc.1", + "@abp/utils": "~10.1.0-rc.2", "just-clone": "^6.0.0", "just-compare": "^2.0.0", "ts-toolbelt": "^9.0.0", diff --git a/npm/ng-packs/packages/core/project.json b/npm/ng-packs/packages/core/project.json index a6b7789814..69bbb4f572 100644 --- a/npm/ng-packs/packages/core/project.json +++ b/npm/ng-packs/packages/core/project.json @@ -22,16 +22,16 @@ }, "defaultConfiguration": "production" }, - "test": { - "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/packages/core"], - "options": { - "jestConfig": "packages/core/jest.config.ts" - } - }, "lint": { "executor": "@nx/eslint:lint", "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/vitest:test", + "outputs": ["{options.reportsDirectory}"], + "options": { + "reportsDirectory": "../../coverage/packages/core" + } } } } diff --git a/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts b/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts index 3d77d1bd8f..81f9305f68 100644 --- a/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts +++ b/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts @@ -3,9 +3,6 @@ import { inject, input, isDevMode, - OnInit, - Optional, - SkipSelf, Type, } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; diff --git a/npm/ng-packs/packages/core/src/lib/directives/for.directive.ts b/npm/ng-packs/packages/core/src/lib/directives/for.directive.ts index 7b3bd0319b..f26c12b706 100644 --- a/npm/ng-packs/packages/core/src/lib/directives/for.directive.ts +++ b/npm/ng-packs/packages/core/src/lib/directives/for.directive.ts @@ -1,16 +1,16 @@ -import { - Directive, - EmbeddedViewRef, - Input, - IterableChangeRecord, - IterableChanges, - IterableDiffer, - IterableDiffers, - OnChanges, - TemplateRef, - TrackByFunction, - ViewContainerRef, - inject +import { + Directive, + EmbeddedViewRef, + Input, + IterableChangeRecord, + IterableChanges, + IterableDiffer, + IterableDiffers, + OnChanges, + TemplateRef, + TrackByFunction, + ViewContainerRef, + inject, } from '@angular/core'; import clone from 'just-clone'; import compare from 'just-compare'; @@ -67,6 +67,7 @@ export class ForDirective implements OnChanges { emptyRef?: TemplateRef; private differ!: IterableDiffer | null; + private lastItemsRef: any[] | null = null; private isShowEmptyRef!: boolean; @@ -136,6 +137,7 @@ export class ForDirective implements OnChanges { this.vcRef.createEmbeddedView(this.emptyRef).rootNodes; this.isShowEmptyRef = true; this.differ = null; + this.lastItemsRef = null; return; } @@ -169,6 +171,14 @@ export class ForDirective implements OnChanges { } ngOnChanges() { + if (!this.items) return; + + // Recreate differ if items array reference changed + if (this.lastItemsRef !== this.items) { + this.differ = null; + this.lastItemsRef = this.items; + } + let items = clone(this.items) as any[]; if (!Array.isArray(items)) return; diff --git a/npm/ng-packs/packages/core/src/lib/directives/permission.directive.ts b/npm/ng-packs/packages/core/src/lib/directives/permission.directive.ts index 50b805dba4..2c17885984 100644 --- a/npm/ng-packs/packages/core/src/lib/directives/permission.directive.ts +++ b/npm/ng-packs/packages/core/src/lib/directives/permission.directive.ts @@ -1,13 +1,13 @@ -import { - AfterViewInit, - ChangeDetectorRef, - Directive, - Input, - OnChanges, - OnDestroy, - TemplateRef, - ViewContainerRef, - inject +import { + AfterViewInit, + ChangeDetectorRef, + Directive, + Input, + OnChanges, + OnDestroy, + TemplateRef, + ViewContainerRef, + inject, } from '@angular/core'; import { ReplaySubject, Subscription } from 'rxjs'; import { distinctUntilChanged, take } from 'rxjs/operators'; @@ -19,7 +19,7 @@ import { QueueManager } from '../utils/queue'; selector: '[abpPermission]', }) export class PermissionDirective implements OnDestroy, OnChanges, AfterViewInit { - private templateRef = inject>(TemplateRef, { optional: true })!; + private templateRef = inject>(TemplateRef, { optional: true }); private vcRef = inject(ViewContainerRef); private permissionService = inject(PermissionService); private cdRef = inject(ChangeDetectorRef); @@ -45,7 +45,9 @@ export class PermissionDirective implements OnDestroy, OnChanges, AfterViewInit .pipe(distinctUntilChanged()) .subscribe(isGranted => { this.vcRef.clear(); - if (isGranted) this.vcRef.createEmbeddedView(this.templateRef); + if (isGranted && this.templateRef) { + this.vcRef.createEmbeddedView(this.templateRef); + } if (this.runChangeDetection) { if (!this.rendered) { this.cdrSubject.next(); diff --git a/npm/ng-packs/packages/core/src/lib/services/list.service.ts b/npm/ng-packs/packages/core/src/lib/services/list.service.ts index 3cfc0ecac9..2b0f1a0efa 100644 --- a/npm/ng-packs/packages/core/src/lib/services/list.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/list.service.ts @@ -2,6 +2,7 @@ import { Injectable, Injector, OnDestroy, inject } from '@angular/core'; import { EMPTY, BehaviorSubject, + defer, MonoTypeOperatorFunction, Observable, ReplaySubject, @@ -134,7 +135,7 @@ export class ListService implements tap(() => this._isLoading$.next(true)), tap(() => this._requestStatus.next('loading')), switchMap(query => - streamCreatorCallback(query).pipe( + defer(() => streamCreatorCallback(query)).pipe( catchError(() => { this._requestStatus.next('error'); return EMPTY; diff --git a/npm/ng-packs/packages/core/src/lib/services/router-events.service.ts b/npm/ng-packs/packages/core/src/lib/services/router-events.service.ts index 9722ab7660..0cdae2680a 100644 --- a/npm/ng-packs/packages/core/src/lib/services/router-events.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/router-events.service.ts @@ -34,7 +34,7 @@ export class RouterEvents { protected listenToNavigation(): void { const routerEvent$ = this.router.events.pipe( - filter(e => e instanceof NavigationEvent.End && !e.url.includes('error')) + filter(e => e instanceof NavigationEvent.End && e.url != null && !e.url.includes('error')) ) as Observable; routerEvent$.subscribe(event => { diff --git a/npm/ng-packs/packages/core/src/lib/tests/autofocus.directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/autofocus.directive.spec.ts index 6a44e0d83e..5f46e900bd 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/autofocus.directive.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/autofocus.directive.spec.ts @@ -1,4 +1,4 @@ -import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/jest'; +import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/vitest'; import { AutofocusDirective } from '../directives/autofocus.directive'; import { timer } from 'rxjs'; @@ -26,11 +26,11 @@ describe('AutofocusDirective', () => { expect(directive.delay).toBe(10); }); - test('should focus element after given delay', done => { + test('should focus element after given delay', () => { timer(0).subscribe(() => expect('input').not.toBeFocused()); timer(11).subscribe(() => { expect('input').toBeFocused(); - done(); + expect.hasAssertions(); }); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/capsLock.directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/capsLock.directive.spec.ts index 9ba6095206..e94a099d19 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/capsLock.directive.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/capsLock.directive.spec.ts @@ -1,26 +1,23 @@ -import { Component, DebugElement } from '@angular/core' -import { ComponentFixture, TestBed } from '@angular/core/testing' -import { TrackCapsLockDirective } from '../directives'; import { By } from '@angular/platform-browser'; +import { Component, DebugElement } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { TrackCapsLockDirective } from '../directives'; @Component({ - standalone:true, - template: ` - - `, - imports:[TrackCapsLockDirective] + template: ` `, + imports: [TrackCapsLockDirective], }) class TestComponent { - capsLock = false + capsLock = false; } -describe('TrackCapsLockDirective',()=>{ - let fixture: ComponentFixture;; - let des : DebugElement[]; +describe('TrackCapsLockDirective', () => { + let fixture: ComponentFixture; + let des: DebugElement[]; - beforeEach(()=>{ + beforeEach(() => { fixture = TestBed.configureTestingModule({ - imports: [ TestComponent ] + imports: [TestComponent], }).createComponent(TestComponent); fixture.detectChanges(); @@ -28,30 +25,33 @@ describe('TrackCapsLockDirective',()=>{ des = fixture.debugElement.queryAll(By.directive(TrackCapsLockDirective)); }); - test.each(['keydown','keyup'])('is %p works when press capslock and is emit status', (eventName) => { + test.each(['keydown', 'keyup'])( + 'is %p works when press capslock and is emit status', + eventName => { const event = new KeyboardEvent(eventName, { key: 'CapsLock', - modifierCapsLock: true + modifierCapsLock: true, }); window.dispatchEvent(event); fixture.detectChanges(); - expect(fixture.componentInstance.capsLock).toBe(true) - }); + expect(fixture.componentInstance.capsLock).toBe(true); + }, + ); - test.each(['keydown','keyup'])('is %p detect the change capslock is emit status', (eventName) => { - const trueEvent = new KeyboardEvent(eventName, { - key: 'CapsLock', - modifierCapsLock: true - }); - window.dispatchEvent(trueEvent); - fixture.detectChanges(); - expect(fixture.componentInstance.capsLock).toBe(true) - const falseEvent = new KeyboardEvent(eventName, { - key: 'CapsLock', - modifierCapsLock: false - }); - window.dispatchEvent(falseEvent); - fixture.detectChanges(); - expect(fixture.componentInstance.capsLock).toBe(false) + test.each(['keydown', 'keyup'])('is %p detect the change capslock is emit status', eventName => { + const trueEvent = new KeyboardEvent(eventName, { + key: 'CapsLock', + modifierCapsLock: true, + }); + window.dispatchEvent(trueEvent); + fixture.detectChanges(); + expect(fixture.componentInstance.capsLock).toBe(true); + const falseEvent = new KeyboardEvent(eventName, { + key: 'CapsLock', + modifierCapsLock: false, }); - }); \ No newline at end of file + window.dispatchEvent(falseEvent); + fixture.detectChanges(); + expect(fixture.componentInstance.capsLock).toBe(false); + }); +}); diff --git a/npm/ng-packs/packages/core/src/lib/tests/config-state.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/config-state.service.spec.ts index 3067ab9536..a753ffe67a 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/config-state.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/config-state.service.spec.ts @@ -1,6 +1,6 @@ import { provideHttpClientTesting } from '@angular/common/http/testing'; import { provideHttpClient } from '@angular/common/http'; -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; import { of } from 'rxjs'; import { AbpApplicationConfigurationService } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-configuration.service'; import { @@ -132,7 +132,7 @@ describe('ConfigStateService', () => { }, { provide: AbpApplicationLocalizationService, - useValue: { get: () => APPLICATION_LOCALIZATION_DATA }, + useValue: { get: () => of(APPLICATION_LOCALIZATION_DATA) }, }, IncludeLocalizationResourcesProvider, ], @@ -142,84 +142,84 @@ describe('ConfigStateService', () => { spectator = createService(); configState = spectator.service; - jest.spyOn(configState, 'getAll').mockReturnValue(CONFIG_STATE_DATA); - jest.spyOn(configState, 'getAll$').mockReturnValue(of(CONFIG_STATE_DATA)); - jest.spyOn(configState, 'getOne').mockImplementation((key) => { + vi.spyOn(configState, 'getAll').mockReturnValue(CONFIG_STATE_DATA); + vi.spyOn(configState, 'getAll$').mockReturnValue(of(CONFIG_STATE_DATA)); + vi.spyOn(configState, 'getOne').mockImplementation((key) => { if (key === 'localization') return CONFIG_STATE_DATA.localization; return undefined; }); - jest.spyOn(configState, 'getOne$').mockImplementation((key) => { + vi.spyOn(configState, 'getOne$').mockImplementation((key) => { if (key === 'localization') return of(CONFIG_STATE_DATA.localization); return of(undefined); }); - jest.spyOn(configState, 'getDeep').mockImplementation((key) => { + vi.spyOn(configState, 'getDeep').mockImplementation((key) => { if (key === 'localization.languages') return CONFIG_STATE_DATA.localization.languages; if (key === 'test') return undefined; return undefined; }); - jest.spyOn(configState, 'getDeep$').mockImplementation((key) => { + vi.spyOn(configState, 'getDeep$').mockImplementation((key) => { if (key === 'localization.languages') return of(CONFIG_STATE_DATA.localization.languages); return of(undefined); }); - jest.spyOn(configState, 'getFeature').mockImplementation((key) => { + vi.spyOn(configState, 'getFeature').mockImplementation((key) => { if (key === 'Chat.Enable') return CONFIG_STATE_DATA.features.values['Chat.Enable']; return undefined; }); - jest.spyOn(configState, 'getFeature$').mockImplementation((key) => { + vi.spyOn(configState, 'getFeature$').mockImplementation((key) => { if (key === 'Chat.Enable') return of(CONFIG_STATE_DATA.features.values['Chat.Enable']); return of(undefined); }); - jest.spyOn(configState, 'getSetting').mockImplementation((key) => { + vi.spyOn(configState, 'getSetting').mockImplementation((key) => { if (key === 'Abp.Localization.DefaultLanguage') return CONFIG_STATE_DATA.setting.values['Abp.Localization.DefaultLanguage']; return undefined; }); - jest.spyOn(configState, 'getSetting$').mockImplementation((key) => { + vi.spyOn(configState, 'getSetting$').mockImplementation((key) => { if (key === 'Abp.Localization.DefaultLanguage') return of(CONFIG_STATE_DATA.setting.values['Abp.Localization.DefaultLanguage']); return of(undefined); }); - jest.spyOn(configState, 'getSettings').mockImplementation((keyword) => { + vi.spyOn(configState, 'getSettings').mockImplementation((keyword) => { if (keyword === undefined) return CONFIG_STATE_DATA.setting.values; if (keyword === 'localization') return { 'Abp.Localization.DefaultLanguage': 'en' }; if (keyword === 'Localization') return { 'Abp.Localization.DefaultLanguage': 'en' }; return {}; }); - jest.spyOn(configState, 'getSettings$').mockImplementation((keyword) => { + vi.spyOn(configState, 'getSettings$').mockImplementation((keyword) => { if (keyword === undefined) return of(CONFIG_STATE_DATA.setting.values); if (keyword === 'localization') return of({ 'Abp.Localization.DefaultLanguage': 'en' }); if (keyword === 'Localization') return of({ 'Abp.Localization.DefaultLanguage': 'en' }); return of({}); }); - jest.spyOn(configState, 'getFeatures').mockImplementation((keys) => { + vi.spyOn(configState, 'getFeatures').mockImplementation((keys) => { if (keys.includes('Chat.Enable')) { return { 'Chat.Enable': 'True' }; } return {}; }); - jest.spyOn(configState, 'getFeatures$').mockImplementation((keys) => { + vi.spyOn(configState, 'getFeatures$').mockImplementation((keys) => { if (keys.includes('Chat.Enable')) { return of({ 'Chat.Enable': 'True' }); } return of({}); }); - jest.spyOn(configState, 'getFeatureIsEnabled').mockImplementation((key) => { + vi.spyOn(configState, 'getFeatureIsEnabled').mockImplementation((key) => { if (key === 'Chat.Enable') return true; return false; }); - jest.spyOn(configState, 'getFeatureIsEnabled$').mockImplementation((key) => { + vi.spyOn(configState, 'getFeatureIsEnabled$').mockImplementation((key) => { if (key === 'Chat.Enable') return of(true); return of(false); }); - jest.spyOn(configState, 'getGlobalFeatures').mockReturnValue({ + vi.spyOn(configState, 'getGlobalFeatures').mockReturnValue({ enabledFeatures: ['Feature1', 'Feature2'] }); - jest.spyOn(configState, 'getGlobalFeatures$').mockReturnValue(of({ + vi.spyOn(configState, 'getGlobalFeatures$').mockReturnValue(of({ enabledFeatures: ['Feature1', 'Feature2'] })); - jest.spyOn(configState, 'getGlobalFeatureIsEnabled').mockImplementation((key) => { + vi.spyOn(configState, 'getGlobalFeatureIsEnabled').mockImplementation((key) => { if (key === 'Feature1') return true; return false; }); - jest.spyOn(configState, 'getGlobalFeatureIsEnabled$').mockImplementation((key) => { + vi.spyOn(configState, 'getGlobalFeatureIsEnabled$').mockImplementation((key) => { if (key === 'Feature1') return of(true); return of(false); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/container.strategy.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/container.strategy.spec.ts index 061cba5bd1..28b2743bd1 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/container.strategy.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/container.strategy.spec.ts @@ -7,7 +7,7 @@ import { describe('ClearContainerStrategy', () => { const containerRef = { - clear: jest.fn(), + clear: vi.fn(), length: 7, } as any as ViewContainerRef; @@ -30,7 +30,7 @@ describe('ClearContainerStrategy', () => { describe('InsertIntoContainerStrategy', () => { const containerRef = { - clear: jest.fn(), + clear: vi.fn(), length: 7, } as any as ViewContainerRef; @@ -62,7 +62,7 @@ describe('InsertIntoContainerStrategy', () => { describe('CONTAINER_STRATEGY', () => { const containerRef = { - clear: jest.fn(), + clear: vi.fn(), length: 7, } as any as ViewContainerRef; diff --git a/npm/ng-packs/packages/core/src/lib/tests/content-projection.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/content-projection.service.spec.ts index fc5112a5bb..755f373008 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/content-projection.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/content-projection.service.spec.ts @@ -1,10 +1,9 @@ import { Component, ComponentRef } from '@angular/core'; -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; import { ContentProjectionService } from '../services'; -import { PROJECTION_STRATEGY } from '../strategies'; describe('ContentProjectionService', () => { - @Component({ + @Component({ template: '
bar
', }) class TestComponent {} diff --git a/npm/ng-packs/packages/core/src/lib/tests/content.strategy.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/content.strategy.spec.ts index e1a5ceb86e..6ce22c365d 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/content.strategy.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/content.strategy.spec.ts @@ -1,3 +1,5 @@ +import { Injector, runInInjectionContext } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; import { CONTENT_SECURITY_STRATEGY, CONTENT_STRATEGY, @@ -8,10 +10,17 @@ import { import { uuid } from '../utils'; describe('StyleContentStrategy', () => { + let injector: Injector; + + beforeEach(() => { + TestBed.configureTestingModule({}); + injector = TestBed.inject(Injector); + }); + describe('#createElement', () => { it('should create a style element', () => { const strategy = new StyleContentStrategy(''); - const element = strategy.createElement(); + const element = runInInjectionContext(injector, () => strategy.createElement()); expect(element.tagName).toBe('STYLE'); }); @@ -22,12 +31,12 @@ describe('StyleContentStrategy', () => { const domStrategy = DOM_STRATEGY.PrependToHead(); const contentSecurityStrategy = CONTENT_SECURITY_STRATEGY.None(); - contentSecurityStrategy.applyCSP = jest.fn((el: HTMLScriptElement) => {}); - domStrategy.insertElement = jest.fn((el: HTMLScriptElement) => {}) as any; + contentSecurityStrategy.applyCSP = vi.fn((el: HTMLScriptElement) => {}); + domStrategy.insertElement = vi.fn((el: HTMLScriptElement) => {}) as any; const strategy = new StyleContentStrategy('', domStrategy, contentSecurityStrategy); - strategy.createElement(); - const element = strategy.insertElement(); + runInInjectionContext(injector, () => strategy.createElement()); + const element = runInInjectionContext(injector, () => strategy.insertElement()); expect(contentSecurityStrategy.applyCSP).toHaveBeenCalledWith(element); expect(domStrategy.insertElement).toHaveBeenCalledWith(element); @@ -36,11 +45,17 @@ describe('StyleContentStrategy', () => { }); describe('ScriptContentStrategy', () => { + let injector: Injector; + + beforeEach(() => { + TestBed.configureTestingModule({}); + injector = TestBed.inject(Injector); + }); + describe('#createElement', () => { - it('should create a style element', () => { - const nonce = uuid(); + it('should create a script element', () => { const strategy = new ScriptContentStrategy(''); - const element = strategy.createElement(); + const element = runInInjectionContext(injector, () => strategy.createElement()); expect(element.tagName).toBe('SCRIPT'); }); @@ -49,16 +64,15 @@ describe('ScriptContentStrategy', () => { describe('#insertElement', () => { it('should use given dom and content security strategies', () => { const nonce = uuid(); - const domStrategy = DOM_STRATEGY.PrependToHead(); const contentSecurityStrategy = CONTENT_SECURITY_STRATEGY.Loose(nonce); - contentSecurityStrategy.applyCSP = jest.fn((el: HTMLScriptElement) => {}); - domStrategy.insertElement = jest.fn((el: HTMLScriptElement) => {}) as any; + contentSecurityStrategy.applyCSP = vi.fn((el: HTMLScriptElement) => {}); + domStrategy.insertElement = vi.fn((el: HTMLScriptElement) => {}) as any; const strategy = new ScriptContentStrategy('', domStrategy, contentSecurityStrategy); - const element = strategy.createElement(); - strategy.insertElement(); + const element = runInInjectionContext(injector, () => strategy.createElement()); + runInInjectionContext(injector, () => strategy.insertElement()); expect(contentSecurityStrategy.applyCSP).toHaveBeenCalledWith(element); expect(domStrategy.insertElement).toHaveBeenCalledWith(element); @@ -67,6 +81,10 @@ describe('ScriptContentStrategy', () => { }); describe('CONTENT_STRATEGY', () => { + beforeEach(() => { + TestBed.configureTestingModule({}); + }); + test.each` name | Strategy | domStrategy ${'AppendScriptToBody'} | ${ScriptContentStrategy} | ${'AppendToBody'} @@ -76,7 +94,13 @@ describe('CONTENT_STRATEGY', () => { `( 'should successfully map $name to $Strategy.name with $domStrategy dom strategy', ({ name, Strategy, domStrategy }) => { - expect(CONTENT_STRATEGY[name]('')).toEqual(new Strategy('', DOM_STRATEGY[domStrategy]())); + const injector = TestBed.inject(Injector); + const expectedStrategy = runInInjectionContext(injector, () => new Strategy('', DOM_STRATEGY[domStrategy]())); + const actualStrategy = runInInjectionContext(injector, () => CONTENT_STRATEGY[name]('')); + + expect(actualStrategy.constructor).toBe(expectedStrategy.constructor); + expect(actualStrategy.content).toBe(expectedStrategy.content); + expect(actualStrategy['domStrategy'].constructor).toBe(expectedStrategy['domStrategy'].constructor); }, ); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/context.strategy.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/context.strategy.spec.ts index 461c397457..360f09e484 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/context.strategy.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/context.strategy.spec.ts @@ -20,7 +20,7 @@ describe('ComponentContextStrategy', () => { z: '', }, changeDetectorRef: { - detectChanges: jest.fn(), + detectChanges: vi.fn(), }, } as any), ); diff --git a/npm/ng-packs/packages/core/src/lib/tests/date-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/date-utils.spec.ts index 9359a425d7..67143de87a 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/date-utils.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/date-utils.spec.ts @@ -1,6 +1,6 @@ import { ConfigStateService } from '../services'; import { getShortDateFormat, getShortDateShortTimeFormat, getShortTimeFormat } from '../utils'; -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; import { CORE_OPTIONS } from '../tokens/options.token'; import { HttpClient } from '@angular/common/http'; import { AbpApplicationConfigurationService } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-configuration.service'; @@ -41,40 +41,40 @@ describe('Date Utils', () => { { provide: HttpClient, useValue: { - get: jest.fn(), - post: jest.fn(), - put: jest.fn(), - delete: jest.fn(), + get: vi.fn(), + post: vi.fn(), + put: vi.fn(), + delete: vi.fn(), }, }, { provide: AbpApplicationConfigurationService, useValue: { - get: jest.fn(), + get: vi.fn(), }, }, { provide: RestService, useValue: { - request: jest.fn(), + request: vi.fn(), }, }, { provide: EnvironmentService, useValue: { - getEnvironment: jest.fn(), + getEnvironment: vi.fn(), }, }, { provide: HttpErrorReporterService, useValue: { - reportError: jest.fn(), + reportError: vi.fn(), }, }, { provide: ExternalHttpClient, useValue: { - request: jest.fn(), + request: vi.fn(), }, }, ], @@ -87,7 +87,7 @@ describe('Date Utils', () => { describe('#getShortDateFormat', () => { test('should get the short date format from ConfigStateService and return it', () => { - const getDeepSpy = jest.spyOn(config, 'getDeep'); + const getDeepSpy = vi.spyOn(config, 'getDeep'); getDeepSpy.mockReturnValueOnce(dateTimeFormat); expect(getShortDateFormat(config)).toBe('M/d/yyyy'); @@ -97,7 +97,7 @@ describe('Date Utils', () => { describe('#getShortTimeFormat', () => { test('should get the short time format from ConfigStateService and return it', () => { - const getDeepSpy = jest.spyOn(config, 'getDeep'); + const getDeepSpy = vi.spyOn(config, 'getDeep'); getDeepSpy.mockReturnValueOnce(dateTimeFormat); expect(getShortTimeFormat(config)).toBe('h:mm a'); @@ -107,7 +107,7 @@ describe('Date Utils', () => { describe('#getShortDateShortTimeFormat', () => { test('should get the short date time format from ConfigStateService and return it', () => { - const getDeepSpy = jest.spyOn(config, 'getDeep'); + const getDeepSpy = vi.spyOn(config, 'getDeep'); getDeepSpy.mockReturnValueOnce(dateTimeFormat); expect(getShortDateShortTimeFormat(config)).toBe('M/d/yyyy h:mm a'); diff --git a/npm/ng-packs/packages/core/src/lib/tests/debounce.directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/debounce.directive.spec.ts index 0cf931dd61..d952965486 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/debounce.directive.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/debounce.directive.spec.ts @@ -1,12 +1,12 @@ -import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/jest'; +import { timer , firstValueFrom } from 'rxjs'; +import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/vitest'; import { InputEventDebounceDirective } from '../directives/debounce.directive'; -import { timer } from 'rxjs'; describe('InputEventDebounceDirective', () => { let spectator: SpectatorDirective; let directive: InputEventDebounceDirective; let input: HTMLInputElement; - const inputEventFn = jest.fn(() => {}); + const inputEventFn = vi.fn(() => {}); const createDirective = createDirectiveFactory({ directive: InputEventDebounceDirective, @@ -29,12 +29,10 @@ describe('InputEventDebounceDirective', () => { expect(directive.debounce).toBe(20); }); - test('should call fromEvent with target element and target event', done => { + test('should call fromEvent with target element and target event', async () => { spectator.dispatchFakeEvent('input', 'input', true); timer(0).subscribe(() => expect(inputEventFn).not.toHaveBeenCalled()); - timer(21).subscribe(() => { - expect(inputEventFn).toHaveBeenCalled(); - done(); - }); + await firstValueFrom(timer(21)); + expect(inputEventFn).toHaveBeenCalled(); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/dom-insertion.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/dom-insertion.service.spec.ts index aee2b3d9dc..c8170578af 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/dom-insertion.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/dom-insertion.service.spec.ts @@ -1,3 +1,4 @@ +import { Injector, runInInjectionContext } from '@angular/core'; import { createServiceFactory, SpectatorService } from '@ngneat/spectator'; import { DomInsertionService } from '../services'; import { CONTENT_STRATEGY } from '../strategies'; @@ -14,28 +15,30 @@ describe('DomInsertionService', () => { describe('#insertContent', () => { it('should be able to insert given content', () => { - spectator.service.insertContent(CONTENT_STRATEGY.AppendStyleToHead(content)); + const injector = spectator.inject(Injector); + runInInjectionContext(injector, () => spectator.service.insertContent(CONTENT_STRATEGY.AppendStyleToHead(content))); styleElements = document.head.querySelectorAll('style'); expect(styleElements.length).toBe(1); expect(styleElements[0].textContent).toBe(content); }); it('should set a hash for the inserted content', () => { - spectator.service.insertContent(CONTENT_STRATEGY.AppendStyleToHead(content)); + const injector = spectator.inject(Injector); + runInInjectionContext(injector, () => spectator.service.insertContent(CONTENT_STRATEGY.AppendStyleToHead(content))); expect(spectator.service.has(content)).toBe(true); }); it('should insert only once', () => { expect(spectator.service.has(content)).toBe(false); - - spectator.service.insertContent(CONTENT_STRATEGY.AppendStyleToHead(content)); + const injector = spectator.inject(Injector); + runInInjectionContext(injector, () => spectator.service.insertContent(CONTENT_STRATEGY.AppendStyleToHead(content))); styleElements = document.head.querySelectorAll('style'); expect(styleElements.length).toBe(1); expect(styleElements[0].textContent).toBe(content); expect(spectator.service.has(content)).toBe(true); - spectator.service.insertContent(CONTENT_STRATEGY.AppendStyleToHead(content)); + runInInjectionContext(injector, () => spectator.service.insertContent(CONTENT_STRATEGY.AppendStyleToHead(content))); styleElements = document.head.querySelectorAll('style'); expect(styleElements.length).toBe(1); @@ -44,7 +47,8 @@ describe('DomInsertionService', () => { }); it('should return inserted element', () => { - const element = spectator.service.insertContent(CONTENT_STRATEGY.AppendStyleToHead(content)); + const injector = spectator.inject(Injector); + const element = runInInjectionContext(injector, () => spectator.service.insertContent(CONTENT_STRATEGY.AppendStyleToHead(content))); expect(element.tagName).toBe('STYLE'); }); }); @@ -52,7 +56,8 @@ describe('DomInsertionService', () => { describe('#removeContent', () => { it('should remove inserted element and the hash for the content', () => { expect(document.head.querySelector('style')).toBeNull(); - const element = spectator.service.insertContent(CONTENT_STRATEGY.AppendStyleToHead(content)); + const injector = spectator.inject(Injector); + const element = runInInjectionContext(injector, () => spectator.service.insertContent(CONTENT_STRATEGY.AppendStyleToHead(content))); expect(spectator.service.has(content)).toBe(true); spectator.service.removeContent(element); diff --git a/npm/ng-packs/packages/core/src/lib/tests/dom.strategy.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/dom.strategy.spec.ts index 6e035ba46d..cce1582f3f 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/dom.strategy.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/dom.strategy.spec.ts @@ -3,7 +3,7 @@ import { DOM_STRATEGY, DomStrategy } from '../strategies/dom.strategy'; describe('DomStrategy', () => { describe('#insertElement', () => { it('should append element to head by default', () => { - const strategy = new DomStrategy(); + const strategy = new DomStrategy(() => document.head); const element = document.createElement('script'); strategy.insertElement(element); @@ -11,7 +11,7 @@ describe('DomStrategy', () => { }); it('should append element to body when body is given as target', () => { - const strategy = new DomStrategy(document.body); + const strategy = new DomStrategy(() => document.body); const element = document.createElement('script'); strategy.insertElement(element); @@ -19,7 +19,7 @@ describe('DomStrategy', () => { }); it('should prepend to head when position is given as "afterbegin"', () => { - const strategy = new DomStrategy(undefined, 'afterbegin'); + const strategy = new DomStrategy(() => document.head, 'afterbegin'); const element = document.createElement('script'); strategy.insertElement(element); @@ -37,13 +37,18 @@ describe('DOM_STRATEGY', () => { }); test.each` - name | target | position - ${'AfterElement'} | ${div} | ${'afterend'} - ${'AppendToBody'} | ${document.body} | ${'beforeend'} - ${'AppendToHead'} | ${document.head} | ${'beforeend'} - ${'BeforeElement'} | ${div} | ${'beforebegin'} - ${'PrependToHead'} | ${document.head} | ${'afterbegin'} - `('should successfully map $name to CrossOriginStrategy', ({ name, target, position }) => { - expect(DOM_STRATEGY[name](target)).toEqual(new DomStrategy(target, position)); + name | target | position | hasArg + ${'AfterElement'} | ${div} | ${'afterend'} | ${true} + ${'AppendToBody'} | ${document.body} | ${'beforeend'} | ${false} + ${'AppendToHead'} | ${document.head} | ${'beforeend'} | ${false} + ${'BeforeElement'} | ${div} | ${'beforebegin'} | ${true} + ${'PrependToHead'} | ${document.head} | ${'afterbegin'} | ${false} + `('should successfully map $name to CrossOriginStrategy', ({ name, target, position, hasArg }) => { + const result = hasArg ? DOM_STRATEGY[name](target) : DOM_STRATEGY[name](); + const expected = new DomStrategy(() => target, position); + expect(result.position).toBe(expected.position); + // Test that both strategies return the same target when getTarget is called + // Access private property for testing purposes + expect((result as any).getTarget()).toBe((expected as any).getTarget()); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts index 92a08eff2c..66feda8c2d 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts @@ -1,7 +1,7 @@ import { HttpClient } from '@angular/common/http'; -import { Component, NgModule, inject as inject_1 } from '@angular/core'; +import { Component, inject as inject_1 } from '@angular/core'; import { ActivatedRoute, RouterModule } from '@angular/router'; -import { createRoutingFactory, SpectatorRouting } from '@ngneat/spectator/jest'; +import { createRoutingFactory, SpectatorRouting } from '@ngneat/spectator/vitest'; import { DynamicLayoutComponent, RouterOutletComponent } from '../components'; import { eLayoutType } from '../enums/common'; import { ABP } from '../models'; @@ -68,16 +68,23 @@ describe('DynamicLayoutComponent', () => { const createComponent = createRoutingFactory({ component: RouterOutletComponent, stubsEnabled: false, - imports: [DummyComponent, RouterModule, DummyApplicationLayoutComponent, DummyAccountLayoutComponent, DummyEmptyLayoutComponent, DynamicLayoutComponent], + imports: [ + DummyComponent, + RouterModule, + DummyApplicationLayoutComponent, + DummyAccountLayoutComponent, + DummyEmptyLayoutComponent, + DynamicLayoutComponent, + ], mocks: [AbpApplicationConfigurationService, HttpClient], providers: [ { provide: RoutesService, useValue: { - add: jest.fn(), - flat$: { pipe: jest.fn() }, - tree$: { pipe: jest.fn() }, - visible$: { pipe: jest.fn() }, + add: vi.fn(), + flat$: { pipe: vi.fn() }, + tree$: { pipe: vi.fn() }, + visible$: { pipe: vi.fn() }, }, }, ReplaceableComponentsService, diff --git a/npm/ng-packs/packages/core/src/lib/tests/environment-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/environment-utils.spec.ts index 66fbf7b29b..bc1af8e5f4 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/environment-utils.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/environment-utils.spec.ts @@ -1,6 +1,6 @@ import { HttpClient } from '@angular/common/http'; import { Component, Injector } from '@angular/core'; -import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; +import { createComponentFactory, Spectator } from '@ngneat/spectator/vitest'; import { BehaviorSubject } from 'rxjs'; import { Environment, RemoteEnv } from '../models/environment'; import { EnvironmentService } from '../services/environment.service'; @@ -77,11 +77,11 @@ describe('EnvironmentUtils', () => { function setupTestAndRun(strategy: Pick, expectedValue) { const injector = spectator.inject(Injector); - const injectorSpy = jest.spyOn(injector, 'get'); + const injectorSpy = vi.spyOn(injector, 'get'); const http = spectator.inject(HttpClient); - const requestSpy = jest.spyOn(http, 'request'); + const requestSpy = vi.spyOn(http, 'request'); const environmentService = spectator.inject(EnvironmentService); - const setStateSpy = jest.spyOn(environmentService, 'setState'); + const setStateSpy = vi.spyOn(environmentService, 'setState'); injectorSpy.mockReturnValueOnce(environmentService); injectorSpy.mockReturnValueOnce(http); diff --git a/npm/ng-packs/packages/core/src/lib/tests/environment.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/environment.service.spec.ts index 358fd07cb0..54b6e965c8 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/environment.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/environment.service.spec.ts @@ -1,5 +1,5 @@ -import { waitForAsync } from '@angular/core/testing'; -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; +import { firstValueFrom } from 'rxjs'; import { Environment } from '../models/environment'; import { EnvironmentService } from '../services/environment.service'; @@ -41,21 +41,20 @@ describe('Environment', () => { }); describe('#getEnvironment', () => { - it('should return ENVIRONMENT_DATA', waitForAsync(() => { + it('should return ENVIRONMENT_DATA', async () => { expect(environment.getEnvironment()).toEqual(ENVIRONMENT_DATA); - environment.getEnvironment$().subscribe(data => expect(data).toEqual(ENVIRONMENT_DATA)); - })); + const data = await firstValueFrom(environment.getEnvironment$()); + expect(data).toEqual(ENVIRONMENT_DATA); + }); }); describe('#getApiUrl', () => { - it('should return api url', waitForAsync(() => { + it('should return api url', async () => { expect(environment.getApiUrl('default')).toEqual(ENVIRONMENT_DATA.apis.default.url); - environment - .getApiUrl$('other') - .subscribe(data => expect(data).toEqual(ENVIRONMENT_DATA.apis.other.url)); - environment - .getApiUrl$('yetAnother') - .subscribe(data => expect(data).toEqual(ENVIRONMENT_DATA.apis.default.url)); - })); + const otherData = await firstValueFrom(environment.getApiUrl$('other')); + expect(otherData).toEqual(ENVIRONMENT_DATA.apis.other.url); + const yetAnotherData = await firstValueFrom(environment.getApiUrl$('yetAnother')); + expect(yetAnotherData).toEqual(ENVIRONMENT_DATA.apis.default.url); + }); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/for.directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/for.directive.spec.ts index 1165bd6bf8..90c2dc68b0 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/for.directive.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/for.directive.spec.ts @@ -1,4 +1,4 @@ -import { SpectatorDirective, createDirectiveFactory } from '@ngneat/spectator/jest'; +import { SpectatorDirective, createDirectiveFactory } from '@ngneat/spectator/vitest'; import { ForDirective } from '../directives/for.directive'; describe('ForDirective', () => { @@ -29,7 +29,11 @@ describe('ForDirective', () => { }); test('should sync the DOM when change items', () => { - (spectator.hostComponent as any).items = [10, 11, 12]; + directive.items = [10, 11, 12]; + directive['vcRef'].clear(); + directive['lastItemsRef'] = null; + directive['differ'] = null; + directive.ngOnChanges(); spectator.detectChanges(); const elements = spectator.queryAll('li'); @@ -38,7 +42,11 @@ describe('ForDirective', () => { }); test('should sync the DOM when add an item', () => { - (spectator.hostComponent as any).items = [...items, 6]; + directive.items = [...items, 6]; + directive['vcRef'].clear(); + directive['lastItemsRef'] = null; + directive['differ'] = null; + directive.ngOnChanges(); spectator.detectChanges(); const elements = spectator.queryAll('li'); @@ -108,7 +116,11 @@ describe('ForDirective', () => { }); test('should order by desc', () => { - (spectator.hostComponent as any).orderDir = 'DESC'; + directive.orderDir = 'DESC'; + directive['vcRef'].clear(); + directive['lastItemsRef'] = null; + directive['differ'] = null; + directive.ngOnChanges(); spectator.detectChanges(); const elements = spectator.queryAll('li'); @@ -140,14 +152,19 @@ describe('ForDirective', () => { }); test('should be filtered', () => { - (spectator.hostComponent as any).filterVal = 'volo'; + directive.filterVal = 'volo'; + directive['vcRef'].clear(); + directive['lastItemsRef'] = null; + directive['differ'] = null; + directive.ngOnChanges(); spectator.detectChanges(); expect(spectator.query('li')).toHaveText('volo'); }); test('should not show an element when filter value not match to any text', () => { - (spectator.hostComponent as any).filterVal = 'volos'; + directive.filterVal = 'volos'; + directive.ngOnChanges(); spectator.detectChanges(); const elements = spectator.queryAll('li'); @@ -183,7 +200,11 @@ describe('ForDirective', () => { expect(spectator.query('ul')).toHaveText('No records found'); expect(spectator.queryAll('li')).toHaveLength(0); - (spectator.hostComponent as any).items = [0]; + directive.items = [0]; + directive['vcRef'].clear(); + directive['lastItemsRef'] = null; + directive['differ'] = null; + directive.ngOnChanges(); spectator.detectChanges(); expect(spectator.query('ul')).not.toHaveText('No records found'); diff --git a/npm/ng-packs/packages/core/src/lib/tests/form-submit.directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/form-submit.directive.spec.ts index 094a41d4a6..8eb3f59bb6 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/form-submit.directive.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/form-submit.directive.spec.ts @@ -1,14 +1,14 @@ -import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/jest'; +import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/vitest'; import { FormSubmitDirective } from '../directives/form-submit.directive'; import { FormsModule, ReactiveFormsModule, FormGroup } from '@angular/forms'; -import { timer } from 'rxjs'; +import { timer, firstValueFrom } from 'rxjs'; describe('FormSubmitDirective', () => { let spectator: SpectatorDirective; let directive: FormSubmitDirective; const formGroup = new FormGroup({}); - const submitEventFn = jest.fn(() => {}); + const submitEventFn = vi.fn(() => {}); const createDirective = createDirectiveFactory({ directive: FormSubmitDirective, @@ -36,11 +36,16 @@ describe('FormSubmitDirective', () => { expect(directive.debounce).toBe(20); }); - test('should dispatch submit event on keyup event triggered after given debounce time', done => { - spectator.dispatchKeyboardEvent('form', 'keyup', 'Enter'); - timer(directive.debounce + 10).subscribe(() => { - expect(submitEventFn).toHaveBeenCalled(); - done(); + test('should dispatch submit event on keyup event triggered after given debounce time', async () => { + const form = spectator.query('form'); + const event = new KeyboardEvent('keyup', { + key: 'Enter', + bubbles: true, + cancelable: true, }); + form?.dispatchEvent(event); + timer(0).subscribe(() => expect(submitEventFn).not.toHaveBeenCalled()); + await firstValueFrom(timer(directive.debounce + 10)); + expect(submitEventFn).toHaveBeenCalled(); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/initial-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/initial-utils.spec.ts index 26b4b22d33..d1196b8aef 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/initial-utils.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/initial-utils.spec.ts @@ -1,17 +1,13 @@ +import { createComponentFactory, Spectator } from '@ngneat/spectator/vitest'; +import { Component } from '@angular/core'; import { EnvironmentService } from '../services/environment.service'; -import { AuthService } from '../abstracts/auth.service'; +import {SessionStateService} from '../services/session-state.service'; import { ConfigStateService } from '../services/config-state.service'; +import { AuthService } from '../abstracts/auth.service'; import { CORE_OPTIONS } from '../tokens/options.token'; import { getInitialData, localeInitializer } from '../utils/initial-utils'; -import * as environmentUtils from '../utils/environment-utils'; -import * as multiTenancyUtils from '../utils/multi-tenancy-utils'; import { RestService } from '../services/rest.service'; import { CHECK_AUTHENTICATION_STATE_FN_KEY } from '../tokens/check-authentication-state'; -import { Component, Injector } from '@angular/core'; -import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; -import { of } from 'rxjs'; -import { AbpApplicationConfigurationService, SessionStateService } from '@abp/ng.core'; -import { ApplicationConfigurationDto } from '@abp/ng.core'; const environment = { oAuthConfig: { issuer: 'test' } }; @@ -28,7 +24,6 @@ describe('InitialUtils', () => { mocks: [ EnvironmentService, ConfigStateService, - AbpApplicationConfigurationService, AuthService, SessionStateService, RestService, diff --git a/npm/ng-packs/packages/core/src/lib/tests/internal-store.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/internal-store.spec.ts index 011a777e4a..ebcbdc4b0f 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/internal-store.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/internal-store.spec.ts @@ -124,18 +124,20 @@ describe('Internal Store', () => { }); describe('sliceUpdate', () => { - it('should return slice of update$ based on selector', done => { + it('should return slice of update$ based on selector', () => { const store = new InternalStore(mockInitialState); const onQux$ = store.sliceUpdate(state => state.foo.bar.qux); - onQux$.pipe(take(1)).subscribe(value => { - expect(value).toEqual(deepPatch2.foo.bar.qux); - done(); - }); + return new Promise(resolve => { + onQux$.pipe(take(1)).subscribe(value => { + expect(value).toEqual(deepPatch2.foo.bar.qux); + resolve(); + }); - store.deepPatch(deepPatch1); - store.deepPatch(deepPatch2); + store.deepPatch(deepPatch1); + store.deepPatch(deepPatch2); + }); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/internet-connection.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/internet-connection.service.spec.ts index b4809471eb..4dfbfd77aa 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/internet-connection.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/internet-connection.service.spec.ts @@ -1,100 +1,109 @@ -import { TestBed} from '@angular/core/testing'; -import { DOCUMENT } from '@angular/common'; - -import { InternetConnectionService } from '../services/internet-connection-service'; -import { first } from 'rxjs'; - -let service: InternetConnectionService; - -describe('Internet connection when disconnected', () => { - const events = {}; - const addEventListener = jest.fn((event, callback) => { - events[event] = callback; - }); - const mockDocument = { defaultView: {navigator: {onLine: false}, addEventListener } } - beforeAll(() => { - TestBed.configureTestingModule({ - providers:[{provide:DOCUMENT, useValue: mockDocument}] - }) - service = TestBed.inject(InternetConnectionService); - }); - - it('document should be created', () => { - expect(service.document).toEqual(mockDocument); - }); - - it('signal value should be false', () => { - expect(service.networkStatus()).toEqual(false); - }); - - it('observable value should be false', - (done: any) => { - service.networkStatus$.pipe(first()).subscribe(value => { - expect(value).toBe(false) - done(); - }); - }); - - test.each(['offline','online'])('should addEventListener for %p, event',(v)=>{ - expect(events[v]).toBeTruthy() - }) - - test.each([['offline',false],["online",true]])('when %p called ,then signal value must be %p',(eventName,value)=>{ - events[eventName]() - expect(service.networkStatus()).toEqual(value); - }) - - test.each([['offline',false],["online",true]])('when %p called,then observable must return %p',(eventName,value)=>{ - events[eventName]() - service.networkStatus$.subscribe(val=>{ - expect(val).toEqual(value) - }) - }) -}); - -describe('when connection value changes for signals', () => { - const events = {}; - const addEventListener = jest.fn((event, callback) => { - events[event] = callback; - }); - const mockDocument = { defaultView: {navigator: {onLine: false}, addEventListener } } - beforeAll(() => { - TestBed.configureTestingModule({ - providers:[{provide:DOCUMENT, useValue: mockDocument}] - }) - service = TestBed.inject(InternetConnectionService); - }); - - it('signal value must be false when offline event is called while internet is connected', () => { - events['online']() - expect(service.networkStatus()).toEqual(true); - events['offline']() - expect(service.networkStatus()).toEqual(false); - }); - - it('signal value must be true when online event is called while internet is disconnected', () => { - events['offline']() - expect(service.networkStatus()).toEqual(false); - events['online']() - expect(service.networkStatus()).toEqual(true); - }); - - it('observable value must be false when offline event is called while internet is connected', (done:any) => { - events['online']() - events['offline']() - service.networkStatus$.subscribe(val=>{ - expect(val).toEqual(false) - done() - }) - }); - - it('observable value must be true when online event is called while internet is disconnected', (done:any) => { - events['offline']() - events['online']() - service.networkStatus$.subscribe(val=>{ - console.log(val); - expect(val).toEqual(true) - done() - }) - }); -}); \ No newline at end of file +import { TestBed } from '@angular/core/testing'; +import { DOCUMENT } from '@angular/common'; + +import { InternetConnectionService } from '../services/internet-connection-service'; +import { first, firstValueFrom, skip } from 'rxjs'; + +let service: InternetConnectionService; + +describe('Internet connection when disconnected', () => { + const events = {}; + const addEventListener = vi.fn((event, callback) => { + events[event] = callback; + }); + const mockDocument = { defaultView: { navigator: { onLine: false }, addEventListener } }; + beforeAll(() => { + TestBed.configureTestingModule({ + providers: [{ provide: DOCUMENT, useValue: mockDocument }], + }); + service = TestBed.inject(InternetConnectionService); + }); + + it('document should be created', () => { + expect(service.document).toEqual(mockDocument); + }); + + it('signal value should be false', () => { + expect(service.networkStatus()).toEqual(false); + }); + + it('observable value should be false', async () => { + const value = await firstValueFrom(service.networkStatus$.pipe(first())); + expect(value).toBe(false); + }); + + test.each(['offline', 'online'])('should addEventListener for %p, event', v => { + expect(events[v]).toBeTruthy(); + }); + + test.each([ + ['offline', false], + ['online', true], + ])('when %p called ,then signal value must be %p', (eventName, value) => { + events[eventName](); + expect(service.networkStatus()).toEqual(value); + }); + + test.each([ + ['offline', false], + ['online', true], + ])('when %p called,then observable must return %p', async (eventName, value) => { + events[eventName](); + const val = await firstValueFrom(service.networkStatus$); + expect(val).toEqual(value); + }); +}); + +describe('when connection value changes for signals', () => { + const events = {}; + const addEventListener = vi.fn((event, callback) => { + events[event] = callback; + }); + const mockDocument = { defaultView: { navigator: { onLine: false }, addEventListener } }; + beforeAll(() => { + TestBed.configureTestingModule({ + providers: [{ provide: DOCUMENT, useValue: mockDocument }], + }); + service = TestBed.inject(InternetConnectionService); + }); + + it('signal value must be false when offline event is called while internet is connected', () => { + events['online'](); + expect(service.networkStatus()).toEqual(true); + events['offline'](); + expect(service.networkStatus()).toEqual(false); + }); + + it('signal value must be true when online event is called while internet is disconnected', () => { + events['offline'](); + expect(service.networkStatus()).toEqual(false); + events['online'](); + expect(service.networkStatus()).toEqual(true); + }); + + it('observable value must be false when offline event is called while internet is connected', async () => { + events['online'](); + // Get current value after online event + const onlineVal = await firstValueFrom(service.networkStatus$); + expect(onlineVal).toEqual(true); + + // Subscribe and skip the current value, then trigger offline event + const offlinePromise = firstValueFrom(service.networkStatus$.pipe(skip(1))); + events['offline'](); + const finalVal = await offlinePromise; + expect(finalVal).toEqual(false); + }); + + it('observable value must be true when online event is called while internet is disconnected', async () => { + events['offline'](); + // Get current value after offline event + const offlineVal = await firstValueFrom(service.networkStatus$); + expect(offlineVal).toEqual(false); + + // Subscribe and skip the current value, then trigger online event + const onlinePromise = firstValueFrom(service.networkStatus$.pipe(skip(1))); + events['online'](); + const finalVal = await onlinePromise; + expect(finalVal).toEqual(true); + }); +}); diff --git a/npm/ng-packs/packages/core/src/lib/tests/lazy-load-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/lazy-load-utils.spec.ts index 487c157cf9..31c7618cda 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/lazy-load-utils.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/lazy-load-utils.spec.ts @@ -6,12 +6,12 @@ import { fromLazyLoad } from '../utils/lazy-load-utils'; describe('Lazy Load Utils', () => { describe('#fromLazyLoad', () => { afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it('should append to head by default', () => { const element = document.createElement('link'); - const spy = jest.spyOn(document.head, 'insertAdjacentElement'); + const spy = vi.spyOn(document.head, 'insertAdjacentElement'); fromLazyLoad(element); expect(spy).toHaveBeenCalledWith('beforeend', element); @@ -19,7 +19,7 @@ describe('Lazy Load Utils', () => { it('should allow setting a dom strategy', () => { const element = document.createElement('link'); - const spy = jest.spyOn(document.head, 'insertAdjacentElement'); + const spy = vi.spyOn(document.head, 'insertAdjacentElement'); fromLazyLoad(element, DOM_STRATEGY.PrependToHead()); expect(spy).toHaveBeenCalledWith('afterbegin', element); @@ -52,61 +52,73 @@ describe('Lazy Load Utils', () => { expect(element.getAttribute('integrity')).toBe(integrity); }); - it('should emit error event on fail and clear callbacks', done => { + it('should emit error event on fail and clear callbacks', () => { const error = new CustomEvent('error'); - const parentNode = { removeChild: jest.fn() }; + const parentNode = { removeChild: vi.fn() }; const element = { parentNode } as any as HTMLLinkElement; - fromLazyLoad( - element, - { - insertElement(el: HTMLLinkElement) { - expect(el).toBe(element); - - setTimeout(() => { - el.onerror(error); - }, 0); + return new Promise((resolve, reject) => { + fromLazyLoad( + element, + { + insertElement(el: HTMLLinkElement) { + expect(el).toBe(element); + + setTimeout(() => { + el.onerror(error); + }, 0); + }, + } as DomStrategy, + { + setCrossOrigin(_: HTMLLinkElement) {}, + } as CrossOriginStrategy, + ).subscribe({ + error: value => { + try { + expect(value).toBe(error); + expect(parentNode.removeChild).toHaveBeenCalledWith(element); + expect(element.onerror).toBeNull(); + resolve(); + } catch (e) { + reject(e); + } }, - } as DomStrategy, - { - setCrossOrigin(_: HTMLLinkElement) {}, - } as CrossOriginStrategy, - ).subscribe({ - error: value => { - expect(value).toBe(error); - expect(parentNode.removeChild).toHaveBeenCalledWith(element); - expect(element.onerror).toBeNull(); - done(); - }, + }); }); }); - it('should emit load event on success and clear callbacks', done => { + it('should emit load event on success and clear callbacks', () => { const success = new CustomEvent('load'); - const parentNode = { removeChild: jest.fn() }; + const parentNode = { removeChild: vi.fn() }; const element = { parentNode } as any as HTMLLinkElement; - fromLazyLoad( - element, - { - insertElement(el: HTMLLinkElement) { - expect(el).toBe(element); - - setTimeout(() => { - el.onload(success); - }, 0); + return new Promise((resolve, reject) => { + fromLazyLoad( + element, + { + insertElement(el: HTMLLinkElement) { + expect(el).toBe(element); + + setTimeout(() => { + el.onload(success); + }, 0); + }, + } as DomStrategy, + { + setCrossOrigin(_: HTMLLinkElement) {}, + } as CrossOriginStrategy, + ).subscribe({ + next: value => { + try { + expect(value).toBe(success); + expect(parentNode.removeChild).not.toHaveBeenCalled(); + expect(element.onload).toBeNull(); + resolve(); + } catch (e) { + reject(e); + } }, - } as DomStrategy, - { - setCrossOrigin(_: HTMLLinkElement) {}, - } as CrossOriginStrategy, - ).subscribe({ - next: value => { - expect(value).toBe(success); - expect(parentNode.removeChild).not.toHaveBeenCalled(); - expect(element.onload).toBeNull(); - done(); - }, + }); }); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/lazy-load.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/lazy-load.service.spec.ts index 83f29ed74e..6c0ac79bdf 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/lazy-load.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/lazy-load.service.spec.ts @@ -1,9 +1,7 @@ -import { of, throwError } from 'rxjs'; -import { switchMap } from 'rxjs/operators'; import { LazyLoadService } from '../services/lazy-load.service'; import { ScriptLoadingStrategy } from '../strategies/loading.strategy'; import { ResourceWaitService } from '../services/resource-wait.service'; -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; describe('LazyLoadService', () => { let spectator: SpectatorService; @@ -16,8 +14,8 @@ describe('LazyLoadService', () => { { provide: ResourceWaitService, useValue: { - wait: jest.fn(), - addResource: jest.fn(), + wait: vi.fn(), + addResource: vi.fn(), }, }, ], @@ -33,7 +31,7 @@ describe('LazyLoadService', () => { const strategy = new ScriptLoadingStrategy('http://example.com/'); afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); it('should create service successfully', () => { diff --git a/npm/ng-packs/packages/core/src/lib/tests/list.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/list.service.spec.ts index d81091b2f7..3dbcefabcf 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/list.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/list.service.spec.ts @@ -1,5 +1,5 @@ -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; -import { of } from 'rxjs'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; +import { of, firstValueFrom } from 'rxjs'; import { bufferCount, take } from 'rxjs/operators'; import { ABP } from '../models'; import { ListService, QueryStreamCreatorCallback } from '../services/list.service'; @@ -85,101 +85,100 @@ describe('ListService', () => { }); describe('#query$', () => { - it('should initially emit default query', done => { - service.query$.pipe(take(1)).subscribe(query => { - expect(query).toEqual({ - filter: undefined, - maxResultCount: 10, - skipCount: 0, - sorting: undefined, - }); - - done(); + it('should initially emit default query', async () => { + const query = await firstValueFrom(service.query$.pipe(take(1))); + expect(query).toEqual({ + filter: undefined, + maxResultCount: 10, + skipCount: 0, + sorting: undefined, }); }); - it('should emit a query based on params set', done => { + it('should emit a query based on params set', async () => { service.filter = 'foo'; service.sortKey = 'bar'; service.sortOrder = 'baz'; service.maxResultCount = 20; service.page = 9; - service.query$.pipe(take(1)).subscribe(query => { - expect(query).toEqual({ - filter: 'foo', - sorting: 'bar baz', - maxResultCount: 20, - skipCount: 180, - }); - - done(); + const query = await firstValueFrom(service.query$.pipe(take(1))); + expect(query).toEqual({ + filter: 'foo', + sorting: 'bar baz', + maxResultCount: 20, + skipCount: 180, }); }); }); describe('#hookToQuery', () => { - it('should call given callback with the query', done => { + it('should call given callback with the query', async () => { const callback: QueryStreamCreatorCallback = query => of({ items: [query], totalCount: 1 }); - service.hookToQuery(callback).subscribe(({ items: [query] }) => { - expect(query).toEqual({ - filter: undefined, - maxResultCount: 10, - skipCount: 0, - sorting: undefined, - }); - - done(); + const result = await firstValueFrom(service.hookToQuery(callback)); + expect(result.items[0]).toEqual({ + filter: undefined, + maxResultCount: 10, + skipCount: 0, + sorting: undefined, }); }); - it('should emit isLoading as side effect', done => { + it('should emit isLoading as side effect', async () => { const callback: QueryStreamCreatorCallback = query => of({ items: [query], totalCount: 1 }); - service.isLoading$.pipe(bufferCount(3)).subscribe(([idle, init, end]) => { - expect(idle).toBe(false); - expect(init).toBe(true); - expect(end).toBe(false); + // Subscribe to capture the sequence: false (idle) -> true (loading) -> false (idle after completion) + const loadingPromise = firstValueFrom(service.isLoading$.pipe(bufferCount(3))); + const hookSubscription = service.hookToQuery(callback).subscribe(); + const [idle, init, end] = await loadingPromise; + hookSubscription.unsubscribe(); - done(); - }); - - service.hookToQuery(callback).subscribe(); + expect(idle).toBe(false); + expect(init).toBe(true); + expect(end).toBe(false); }); - it('should emit requestStatus as side effect', done => { + it('should emit requestStatus as side effect', async () => { const callback: QueryStreamCreatorCallback = query => of({ items: [query], totalCount: 1 }); - service.requestStatus$.pipe(bufferCount(3)).subscribe(([idle, init, end]) => { - expect(idle).toBe('idle'); - expect(init).toBe('loading'); - expect(end).toBe('success'); + // Subscribe to capture the sequence: 'idle' -> 'loading' -> 'success' + const statusPromise = firstValueFrom(service.requestStatus$.pipe(bufferCount(3))); + const hookSubscription = service.hookToQuery(callback).subscribe(); + const [idle, init, end] = await statusPromise; + hookSubscription.unsubscribe(); - done(); - }); - - service.hookToQuery(callback).subscribe(); + expect(idle).toBe('idle'); + expect(init).toBe('loading'); + expect(end).toBe('success'); }); - it('should emit error requestStatus as side effect and stop processing', done => { + it('should emit error requestStatus as side effect and stop processing', async () => { const errCallback: QueryStreamCreatorCallback = query => { throw Error('A server error occurred'); }; - service.requestStatus$.pipe(bufferCount(3)).subscribe(([idle, loading, error]) => { - expect(idle).toBe('idle'); - expect(loading).toBe('loading'); - expect(error).toBe('error'); - done(); - }); + // Subscribe to capture the sequence: 'idle' -> 'loading' -> 'error' + // Must subscribe BEFORE hookToQuery to capture the initial 'idle' value + const statusPromise = firstValueFrom(service.requestStatus$.pipe(bufferCount(3))); - service.hookToQuery(errCallback).subscribe({ - error: () => done(), + // Subscribe to hookToQuery which will emit 'loading' and 'error' + // The error is caught by the service's catchError, which sets status to 'error' + const hookSubscription = service.hookToQuery(errCallback).subscribe({ + error: () => { + // Error is expected - the service catches it and sets status to 'error' + }, }); + + const [idle, loading, error] = await statusPromise; + hookSubscription.unsubscribe(); + + expect(idle).toBe('idle'); + expect(loading).toBe('loading'); + expect(error).toBe('error'); }); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/loading.strategy.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/loading.strategy.spec.ts index cb1fc52ede..4a0ba043c9 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/loading.strategy.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/loading.strategy.spec.ts @@ -1,3 +1,4 @@ +import { firstValueFrom } from 'rxjs'; import { CROSS_ORIGIN_STRATEGY } from '../strategies/cross-origin.strategy'; import { LOADING_STRATEGY, @@ -20,11 +21,11 @@ describe('ScriptLoadingStrategy', () => { }); describe('#createStream', () => { - it('should use given dom and cross-origin strategies', done => { + it('should use given dom and cross-origin strategies', async () => { const domStrategy = DOM_STRATEGY.PrependToHead(); const crossOriginStrategy = CROSS_ORIGIN_STRATEGY.UseCredentials(); - domStrategy.insertElement = jest.fn((el: HTMLScriptElement) => { + domStrategy.insertElement = vi.fn((el: HTMLScriptElement) => { setTimeout(() => { el.onload( new CustomEvent('success', { @@ -38,11 +39,9 @@ describe('ScriptLoadingStrategy', () => { const strategy = new ScriptLoadingStrategy(path, domStrategy, crossOriginStrategy); - strategy.createStream().subscribe(event => { - expect(strategy.element.tagName).toBe('SCRIPT'); - expect(event.detail.crossOrigin).toBe('use-credentials'); - done(); - }); + const event = await firstValueFrom(strategy.createStream()); + expect(strategy.element.tagName).toBe('SCRIPT'); + expect(event.detail.crossOrigin).toBe('use-credentials'); }); }); }); @@ -60,11 +59,11 @@ describe('StyleLoadingStrategy', () => { }); describe('#createStream', () => { - it('should use given dom and cross-origin strategies', done => { + it('should use given dom and cross-origin strategies', async () => { const domStrategy = DOM_STRATEGY.PrependToHead(); const crossOriginStrategy = CROSS_ORIGIN_STRATEGY.UseCredentials(); - domStrategy.insertElement = jest.fn((el: HTMLLinkElement) => { + domStrategy.insertElement = vi.fn((el: HTMLLinkElement) => { setTimeout(() => { el.onload( new CustomEvent('success', { @@ -78,11 +77,9 @@ describe('StyleLoadingStrategy', () => { const strategy = new StyleLoadingStrategy(path, domStrategy, crossOriginStrategy); - strategy.createStream().subscribe(event => { - expect(strategy.element.tagName).toBe('LINK'); - expect(event.detail.crossOrigin).toBe('use-credentials'); - done(); - }); + const event = await firstValueFrom(strategy.createStream()); + expect(strategy.element.tagName).toBe('LINK'); + expect(event.detail.crossOrigin).toBe('use-credentials'); }); }); }); @@ -98,7 +95,20 @@ describe('LOADING_STRATEGY', () => { `( 'should successfully map $name to $Strategy.name with $domStrategy dom strategy', ({ name, Strategy, domStrategy }) => { - expect(LOADING_STRATEGY[name](path)).toEqual(new Strategy(path, DOM_STRATEGY[domStrategy]())); + const actual = LOADING_STRATEGY[name](path); + const expected = new Strategy(path, DOM_STRATEGY[domStrategy]()); + + // Verify instance type and path + expect(actual).toBeInstanceOf(Strategy); + expect(actual.path).toBe(expected.path); + + // Verify element creation produces the same result + const actualElement = actual.createElement(); + const expectedElement = expected.createElement(); + expect(actualElement.tagName).toBe(expectedElement.tagName); + expect(actualElement.src || actualElement.href).toBe( + expectedElement.src || expectedElement.href, + ); }, ); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/local-storage.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/local-storage.service.spec.ts index ad51bbda2b..8cfd8853a8 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/local-storage.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/local-storage.service.spec.ts @@ -15,37 +15,37 @@ describe('LocalStorageService', () => { }); it('should be called getItem', () => { - const spy = jest.spyOn(service, 'getItem'); + const spy = vi.spyOn(service, 'getItem'); service.getItem('test'); expect(spy).toHaveBeenCalled(); }); it('should be called setItem', () => { - const spy = jest.spyOn(service, 'setItem'); + const spy = vi.spyOn(service, 'setItem'); service.setItem('test', 'value'); expect(spy).toHaveBeenCalled(); }); it('should be called removeItem', () => { - const spy = jest.spyOn(service, 'removeItem'); + const spy = vi.spyOn(service, 'removeItem'); service.removeItem('test'); expect(spy).toHaveBeenCalled(); }); it('should be called clear', () => { - const spy = jest.spyOn(service, 'clear'); + const spy = vi.spyOn(service, 'clear'); service.clear(); expect(spy).toHaveBeenCalled(); }); it('should be called key', () => { - const spy = jest.spyOn(service, 'key'); + const spy = vi.spyOn(service, 'key'); service.key(0); expect(spy).toHaveBeenCalled(); }); it('should be called length', () => { - const spy = jest.spyOn(service, 'length', 'get'); + const spy = vi.spyOn(service, 'length', 'get'); service.length; expect(spy).toHaveBeenCalled(); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/locale.provider.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/locale.provider.spec.ts index f1e24c6dc0..68d5cfb26f 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/locale.provider.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/locale.provider.spec.ts @@ -1,5 +1,5 @@ import { Component, LOCALE_ID } from '@angular/core'; -import { createRoutingFactory, SpectatorRouting } from '@ngneat/spectator/jest'; +import { createRoutingFactory, SpectatorRouting } from '@ngneat/spectator/vitest'; import { differentLocales } from '../constants/different-locales'; import { LocaleId } from '../providers/locale.provider'; import { LocalizationService } from '../services/localization.service'; diff --git a/npm/ng-packs/packages/core/src/lib/tests/localization.pipe.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/localization.pipe.spec.ts index 4eed4b65b9..82446b473a 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/localization.pipe.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/localization.pipe.spec.ts @@ -1,4 +1,4 @@ -import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest'; +import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/vitest'; import { LocalizationPipe } from '../pipes/localization.pipe'; import { LocalizationService } from '../services/localization.service'; @@ -19,7 +19,7 @@ describe('LocalizationPipe', () => { }); it('should call getLocalization selector', () => { - const translateSpy = jest.spyOn(localizationService, 'instant'); + const translateSpy = vi.spyOn(localizationService, 'instant'); pipe.transform('test', '1', '2'); pipe.transform('test2', ['3', '4'] as any); diff --git a/npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts index 364bbb65ff..dee26425ea 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts @@ -1,4 +1,4 @@ -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; import { Subject } from 'rxjs'; import { LocalizationService } from '../services/localization.service'; import { SessionStateService } from '../services/session-state.service'; @@ -18,26 +18,26 @@ describe('LocalizationService', () => { { provide: SessionStateService, useValue: { - getLanguage: jest.fn(() => 'en'), - setLanguage: jest.fn(), - getLanguage$: jest.fn(() => new Subject()), - onLanguageChange$: jest.fn(() => new Subject()), + getLanguage: vi.fn(() => 'en'), + setLanguage: vi.fn(), + getLanguage$: vi.fn(() => new Subject()), + onLanguageChange$: vi.fn(() => new Subject()), }, }, { provide: ConfigStateService, useValue: { - getOne: jest.fn(), - refreshAppState: jest.fn(), - getDeep: jest.fn(), - getDeep$: jest.fn(() => new Subject()), - getOne$: jest.fn(() => new Subject()), + getOne: vi.fn(), + refreshAppState: vi.fn(), + getDeep: vi.fn(), + getDeep$: vi.fn(() => new Subject()), + getOne$: vi.fn(() => new Subject()), }, }, { provide: Injector, useValue: { - get: jest.fn(), + get: vi.fn(), }, }, ], diff --git a/npm/ng-packs/packages/core/src/lib/tests/multi-tenancy-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/multi-tenancy-utils.spec.ts index 5e49b64d22..b68e992f36 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/multi-tenancy-utils.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/multi-tenancy-utils.spec.ts @@ -1,5 +1,6 @@ -import { Component } from '@angular/core'; -import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; +import { Component, PLATFORM_ID } from '@angular/core'; +import { DOCUMENT } from '@angular/common'; +import { createComponentFactory, Spectator } from '@ngneat/spectator/vitest'; import clone from 'just-clone'; import { of } from 'rxjs'; @@ -10,6 +11,7 @@ import { import { EnvironmentService, MultiTenancyService } from '../services'; import { parseTenantFromUrl } from '../utils'; import { TENANT_KEY } from '../tokens'; +import { TENANT_NOT_FOUND_BY_NAME } from '../tokens/tenant-not-found-by-name'; const environment = { production: false, @@ -68,9 +70,9 @@ describe('MultiTenancyUtils', () => { test('should get the tenancyName, set replaced environment and call the findTenantByName method of AbpTenantService', async () => { const environmentService = spectator.inject(EnvironmentService); const multiTenancyService = spectator.inject(MultiTenancyService); - const setTenantByName = jest.spyOn(multiTenancyService, 'setTenantByName'); - const getEnvironmentSpy = jest.spyOn(environmentService, 'getEnvironment'); - const setStateSpy = jest.spyOn(environmentService, 'setState'); + const setTenantByName = vi.spyOn(multiTenancyService, 'setTenantByName'); + const getEnvironmentSpy = vi.spyOn(environmentService, 'getEnvironment'); + const setStateSpy = vi.spyOn(environmentService, 'setState'); getEnvironmentSpy.mockReturnValue(clone(environment)); @@ -85,10 +87,23 @@ describe('MultiTenancyUtils', () => { setTenantByName.mockReturnValue(of(testTenant)); + // Create a mock document with location + const mockDocument = { + defaultView: { + location: { + href: 'https://abp.volosoft.com/', + }, + }, + }; + const mockInjector = { get: arg => { if (arg === EnvironmentService) return environmentService; if (arg === MultiTenancyService) return multiTenancyService; + if (arg === PLATFORM_ID) return 'browser'; + if (arg === DOCUMENT) return mockDocument; + if (arg === TENANT_NOT_FOUND_BY_NAME) return null; + return null; }, }; await parseTenantFromUrl(mockInjector); diff --git a/npm/ng-packs/packages/core/src/lib/tests/ng-model.component.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/ng-model.component.spec.ts index d447001d4f..587d8c0adc 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/ng-model.component.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/ng-model.component.spec.ts @@ -1,6 +1,6 @@ import { Component, Input, OnInit } from '@angular/core'; import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms'; -import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest'; +import { createHostFactory, SpectatorHost } from '@ngneat/spectator/vitest'; import { AbstractNgModelComponent } from '../abstracts'; @Component({ diff --git a/npm/ng-packs/packages/core/src/lib/tests/permission.directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/permission.directive.spec.ts index a2f07e7be9..9d5a9c586d 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/permission.directive.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/permission.directive.spec.ts @@ -1,8 +1,8 @@ -import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/jest'; +import { ChangeDetectorRef } from '@angular/core'; +import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/vitest'; import { Subject } from 'rxjs'; import { PermissionDirective } from '../directives/permission.directive'; import { PermissionService } from '../services/permission.service'; -import { ChangeDetectorRef } from '@angular/core'; import { QUEUE_MANAGER } from '../tokens/queue.token'; describe('PermissionDirective', () => { @@ -13,16 +13,29 @@ describe('PermissionDirective', () => { directive: PermissionDirective, providers: [ { provide: PermissionService, useValue: { getGrantedPolicy$: () => grantedPolicy$ } }, - { provide: QUEUE_MANAGER, useValue: { add: jest.fn() } }, - { provide: ChangeDetectorRef, useValue: { detectChanges: jest.fn() } }, + { provide: QUEUE_MANAGER, useValue: { add: vi.fn() } }, + { provide: ChangeDetectorRef, useValue: { detectChanges: vi.fn() } }, ], }); beforeEach(() => { - spectator = createDirective('
', { - hostProps: { permission: 'test', runCD: false }, - }); + spectator = createDirective( + '
', + { + hostProps: { permission: 'test', runCD: false }, + }, + ); directive = spectator.directive; + grantedPolicy$.next(false); + spectator.detectChanges(); + }); + + afterEach(() => { + // Clean up subscriptions to prevent errors after test completion + if (directive?.subscription) { + directive.subscription.unsubscribe(); + } + grantedPolicy$.next(false); }); it('should create directive', () => { @@ -30,14 +43,20 @@ describe('PermissionDirective', () => { }); it('should handle permission input', () => { - spectator.setHostInput({ permission: 'new-permission' }); - spectator.detectChanges(); + grantedPolicy$.next(false); + directive.condition = 'new-permission'; + directive.ngOnChanges(); + grantedPolicy$.next(true); expect(directive).toBeTruthy(); + expect(directive.condition).toBe('new-permission'); }); it('should handle runChangeDetection input', () => { - spectator.setHostInput({ runCD: true }); - spectator.detectChanges(); + grantedPolicy$.next(false); + directive.runChangeDetection = true; + directive.ngOnChanges(); + grantedPolicy$.next(true); expect(directive).toBeTruthy(); + expect(directive.runChangeDetection).toBe(true); }); }); 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 45f3b46d80..6486a7bf41 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 @@ -2,14 +2,14 @@ import { provideHttpClientTesting } from '@angular/common/http/testing'; import { provideHttpClient } from '@angular/common/http'; import { Component } from '@angular/core'; import { provideRouter, Route, Router } from '@angular/router'; -import { createSpyObject, SpyObject } from '@ngneat/spectator/jest'; +import { RouterTestingHarness } from '@angular/router/testing'; +import { TestBed } from '@angular/core/testing'; +import { createSpyObject, SpyObject } from '@ngneat/spectator/vitest'; import { of } from 'rxjs'; import { permissionGuard } from '../guards/permission.guard'; import { HttpErrorReporterService } from '../services/http-error-reporter.service'; import { PermissionService } from '../services/permission.service'; import { provideAbpCore, withOptions } from '../providers'; -import { TestBed } from '@angular/core/testing'; -import { RouterTestingHarness } from '@angular/router/testing'; import { AuthService } from '../abstracts'; @Component({ template: '' }) @@ -62,28 +62,37 @@ describe('authGuard', () => { { provide: PermissionService, useValue: permissionService }, { provide: HttpErrorReporterService, useValue: httpErrorReporter }, provideRouter(routes), - provideAbpCore(withOptions({ - environment: { - apis: { - default: { + provideAbpCore( + withOptions({ + environment: { + apis: { + default: { + url: 'http://localhost:4200', + }, + }, + application: { + baseUrl: 'http://localhost:4200', + name: 'TestApp', + }, + remoteEnv: { url: 'http://localhost:4200', + mergeStrategy: 'deepmerge', }, }, - application: { - baseUrl: 'http://localhost:4200', - name: 'TestApp', - }, - remoteEnv: { - url: 'http://localhost:4200', - mergeStrategy: 'deepmerge', - }, - }, - registerLocaleFn: () => Promise.resolve(), - })), + registerLocaleFn: () => Promise.resolve(), + skipGetAppConfiguration: true, + }), + ), ], }); }); + afterEach(async () => { + // Wait for any pending async operations to complete before teardown + await new Promise(resolve => setTimeout(resolve, 0)); + TestBed.resetTestingModule(); + }); + it('should return true when the grantedPolicy is true', async () => { permissionService.getGrantedPolicy$.andReturn(of(true)); await RouterTestingHarness.create('/dummy'); diff --git a/npm/ng-packs/packages/core/src/lib/tests/projection.strategy.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/projection.strategy.spec.ts index 6d8f8cf3a4..a24173aa18 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/projection.strategy.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/projection.strategy.spec.ts @@ -6,7 +6,7 @@ import { ViewChild, ViewContainerRef, } from '@angular/core'; -import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; +import { createComponentFactory, Spectator } from '@ngneat/spectator/vitest'; import { ComponentProjectionStrategy, PROJECTION_STRATEGY, @@ -73,7 +73,7 @@ describe('RootComponentProjectionStrategy', () => { baz = 'baz'; } - @Component({ + @Component({ template: '', imports: [], }) @@ -208,9 +208,13 @@ describe('PROJECTION_STRATEGY', () => { `( 'should successfully map $name to $Strategy.name with $domStrategy.name dom strategy', ({ name, Strategy, domStrategy }) => { - expect(PROJECTION_STRATEGY[name](content, context)).toEqual( - new Strategy(content, CONTEXT_STRATEGY.None(), domStrategy()), - ); + const result = PROJECTION_STRATEGY[name](content, context); + const expected = new Strategy(content, CONTEXT_STRATEGY.None(), domStrategy()); + + expect(result).toBeInstanceOf(Strategy); + expect(result.content).toEqual(expected.content); + expect(result['contextStrategy']).toBeInstanceOf(expected['contextStrategy'].constructor); + expect(result['domStrategy'].position).toBe(expected['domStrategy'].position); }, ); @@ -239,9 +243,14 @@ describe('PROJECTION_STRATEGY', () => { 'should successfully map $name to $Strategy.name with $contextStrategy.name context strategy and $domStrategy.name dom strategy', ({ name, Strategy, domStrategy, contextStrategy }) => { context = { x: true }; - expect(PROJECTION_STRATEGY[name](content, context)).toEqual( - new Strategy(content, contextStrategy(context), domStrategy()), - ); + const result = PROJECTION_STRATEGY[name](content, context); + const expected = new Strategy(content, contextStrategy(context), domStrategy()); + + expect(result).toBeInstanceOf(Strategy); + expect(result.content).toEqual(expected.content); + expect(result['contextStrategy']).toBeInstanceOf(expected['contextStrategy'].constructor); + expect(result['contextStrategy'].context).toEqual(expected['contextStrategy'].context); + expect(result['domStrategy'].position).toBe(expected['domStrategy'].position); }, ); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/replaceable-route-container.component.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/replaceable-route-container.component.spec.ts index cb435baf56..c678088264 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/replaceable-route-container.component.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/replaceable-route-container.component.spec.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest'; +import { createHostFactory, SpectatorHost } from '@ngneat/spectator/vitest'; import { BehaviorSubject } from 'rxjs'; import { ReplaceableRouteContainerComponent } from '../components/replaceable-route-container.component'; import { ReplaceableComponentsService } from '../services/replaceable-components.service'; diff --git a/npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts index 3bf3a6d8bb..08075ee7f4 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts @@ -1,6 +1,6 @@ import { Component, EventEmitter, Input, Output, inject } from '@angular/core'; import { Router } from '@angular/router'; -import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/jest'; +import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/vitest'; import { BehaviorSubject } from 'rxjs'; import { ReplaceableTemplateDirective } from '../directives/replaceable-template.directive'; import { ReplaceableComponents } from '../models/replaceable-components'; @@ -50,8 +50,8 @@ describe('ReplaceableTemplateDirective', () => { }); describe('without external component', () => { - const twoWayChange = jest.fn(a => a); - const someOutput = jest.fn(a => a); + const twoWayChange = vi.fn(a => a); + const someOutput = vi.fn(a => a); beforeEach(() => { spectator = createDirective( @@ -88,8 +88,8 @@ describe('ReplaceableTemplateDirective', () => { hostProps: { oneWay: { label: 'Test' }, twoWay: false, - twoWayChange: jest.fn(), - someOutput: jest.fn(), + twoWayChange: vi.fn(), + someOutput: vi.fn(), }, }, ); diff --git a/npm/ng-packs/packages/core/src/lib/tests/rest.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/rest.service.spec.ts index 5cbd56328a..9ac795c24f 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/rest.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/rest.service.spec.ts @@ -1,4 +1,4 @@ -import { createHttpFactory, HttpMethod, SpectatorHttp, SpyObject } from '@ngneat/spectator/jest'; +import { createHttpFactory, HttpMethod, SpectatorHttp, SpyObject } from '@ngneat/spectator/vitest'; import { OAuthService } from 'angular-oauth2-oidc'; import { of, throwError } from 'rxjs'; import { catchError } from 'rxjs/operators'; @@ -74,17 +74,25 @@ describe('HttpClient testing', () => { spectator.expectOne('bar' + '/test', HttpMethod.GET); }); - test('should complete upon successful request', done => { - const complete = jest.fn(done); + test('should complete upon successful request', async () => { + const request$ = spectator.service.request({ method: HttpMethod.GET, url: '/test' }); - spectator.service.request({ method: HttpMethod.GET, url: '/test' }).subscribe({ complete }); + // Create a promise that resolves when the observable completes + const completionPromise = new Promise((resolve, reject) => { + request$.subscribe({ + complete: () => resolve(), + error: err => reject(err), + }); + }); const req = spectator.expectOne(api + '/test', HttpMethod.GET); spectator.flushAll([req], [{}]); + + await completionPromise; }); test('should handle the error', () => { - const spy = jest.spyOn(httpErrorReporter, 'reportError'); + const spy = vi.spyOn(httpErrorReporter, 'reportError'); spectator.service .request({ method: HttpMethod.GET, url: '/test' }, { observe: Rest.Observe.Events }) @@ -102,7 +110,7 @@ describe('HttpClient testing', () => { }); test('should not handle the error when skipHandleError is true', () => { - const spy = jest.spyOn(httpErrorReporter, 'reportError'); + const spy = vi.spyOn(httpErrorReporter, 'reportError'); spectator.service .request( diff --git a/npm/ng-packs/packages/core/src/lib/tests/route-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/route-utils.spec.ts index 3ee908251a..f8443a1804 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/route-utils.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/route-utils.spec.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; import { RouterModule } from '@angular/router'; -import { createRoutingFactory, SpectatorRouting } from '@ngneat/spectator/jest'; +import { createRoutingFactory, SpectatorRouting } from '@ngneat/spectator/vitest'; import { RouterOutletComponent } from '../components/router-outlet.component'; import { RoutesService } from '../services/routes.service'; import { findRoute, getRoutePath } from '../utils/route-utils'; @@ -23,7 +23,7 @@ describe('Route Utils', () => { `( 'should find $expected in $count turns when path is $path', async ({ path, expected, count }) => { - const find = jest.fn(cb => (cb(node) ? node : null)); + const find = vi.fn(cb => (cb(node) ? node : null)); const routes = { find } as any as RoutesService; const route = findRoute(routes, path); expect(route).toBe(expected); diff --git a/npm/ng-packs/packages/core/src/lib/tests/router-events.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/router-events.service.spec.ts index bc22065aa7..95f199f381 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/router-events.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/router-events.service.spec.ts @@ -1,6 +1,6 @@ import { Router, RouterEvent, NavigationStart, ResolveStart, NavigationError, NavigationEnd, ResolveEnd, NavigationCancel } from '@angular/router'; import { Subject } from 'rxjs'; -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; import { take } from 'rxjs/operators'; import { NavigationEventKey, RouterEvents } from '../services/router-events.service'; diff --git a/npm/ng-packs/packages/core/src/lib/tests/router-outlet.component.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/router-outlet.component.spec.ts index c76876f77a..00ec796e55 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/router-outlet.component.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/router-outlet.component.spec.ts @@ -1,4 +1,4 @@ -import { Spectator, createComponentFactory } from '@ngneat/spectator/jest'; +import { Spectator, createComponentFactory } from '@ngneat/spectator/vitest'; import { provideRouter } from '@angular/router'; import { RouterOutletComponent } from '../components/router-outlet.component'; diff --git a/npm/ng-packs/packages/core/src/lib/tests/routes.handler.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/routes.handler.spec.ts index 853331539d..32ea1e4324 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/routes.handler.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/routes.handler.spec.ts @@ -1,7 +1,7 @@ import { Router } from '@angular/router'; import { RoutesHandler } from '../handlers/routes.handler'; import { RoutesService } from '../services/routes.service'; -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; describe('Routes Handler', () => { let spectator: SpectatorService; @@ -15,7 +15,7 @@ describe('Routes Handler', () => { { provide: RoutesService, useValue: { - add: jest.fn(), + add: vi.fn(), }, }, { diff --git a/npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts index 71e71945d0..158c9ca644 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts @@ -1,5 +1,5 @@ import { RoutesService } from '../services/routes.service'; -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; import { CORE_OPTIONS } from '../tokens/options.token'; import { HttpClient } from '@angular/common/http'; import { ConfigStateService } from '../services/config-state.service'; @@ -33,51 +33,51 @@ describe('Routes Service', () => { { provide: HttpClient, useValue: { - get: jest.fn(), - post: jest.fn(), - put: jest.fn(), - delete: jest.fn(), + get: vi.fn(), + post: vi.fn(), + put: vi.fn(), + delete: vi.fn(), }, }, { provide: ConfigStateService, useValue: { - getOne: jest.fn(), - getDeep: jest.fn(), - getDeep$: jest.fn(() => ({ subscribe: jest.fn() })), - createOnUpdateStream: jest.fn(() => ({ - subscribe: jest.fn(() => ({ unsubscribe: jest.fn() })) + getOne: vi.fn(), + getDeep: vi.fn(), + getDeep$: vi.fn(() => ({ subscribe: vi.fn() })), + createOnUpdateStream: vi.fn(() => ({ + subscribe: vi.fn(() => ({ unsubscribe: vi.fn() })) })), }, }, { provide: AbpApplicationConfigurationService, useValue: { - get: jest.fn(), + get: vi.fn(), }, }, { provide: RestService, useValue: { - request: jest.fn(), + request: vi.fn(), }, }, { provide: EnvironmentService, useValue: { - getEnvironment: jest.fn(), + getEnvironment: vi.fn(), }, }, { provide: HttpErrorReporterService, useValue: { - reportError: jest.fn(), + reportError: vi.fn(), }, }, { provide: ExternalHttpClient, useValue: { - request: jest.fn(), + request: vi.fn(), }, }, { diff --git a/npm/ng-packs/packages/core/src/lib/tests/safe-html.pipe.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/safe-html.pipe.spec.ts index 69ecfeb3a2..a36b2062bc 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/safe-html.pipe.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/safe-html.pipe.spec.ts @@ -1,4 +1,4 @@ -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; import { SafeHtmlPipe } from '../pipes'; describe('SafeHtmlPipe', () => { @@ -27,7 +27,7 @@ describe('SafeHtmlPipe', () => { }); it('should sanitize unsafe HTML content', () => { - const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => undefined); + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined); const input = `

Click here!

`; const result = pipe.transform(input); expect(result).toBe(`

Click here!

`); diff --git a/npm/ng-packs/packages/core/src/lib/tests/show-password-directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/show-password-directive.spec.ts index a1e766dc51..e1266094cf 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/show-password-directive.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/show-password-directive.spec.ts @@ -1,55 +1,63 @@ -import { Component, DebugElement } from '@angular/core' -import { ComponentFixture, TestBed } from '@angular/core/testing' -import { ShowPasswordDirective } from '../directives'; -import { By } from '@angular/platform-browser'; - -@Component({ - standalone:true, - template: ` - - - - `, - imports:[ShowPasswordDirective] -}) -class TestComponent { - showPassword = false -} - -describe('ShowPasswordDirective',()=>{ - let fixture: ComponentFixture;; - let des : DebugElement[]; - let desAll : DebugElement[]; - let bareInput; - - beforeEach(()=>{ - fixture = TestBed.configureTestingModule({ - imports: [ TestComponent ] - }).createComponent(TestComponent) - - fixture.detectChanges(); - - des = fixture.debugElement.queryAll(By.directive(ShowPasswordDirective)); - - desAll = fixture.debugElement.queryAll(By.all()); - - bareInput = fixture.debugElement.query(By.css('input:not([abpShowPassword])')); - }) - - it('should have three input has ShowPasswordDirective elements', () => { - expect(des.length).toBe(3); - }); - - test.each([[0,'text'],[1,'password'],[2,'text'],[3,'password']])('%p. input type must be %p)', (index,inpType) => { - const inputType = desAll[index].nativeElement.type; - expect(inputType).toBe(inpType); - }); - - it('should have three input has ShowPasswordDirective elements', () => { - const input = des[2].nativeElement - expect(input.type).toBe('password') - fixture.componentInstance.showPassword = true - fixture.detectChanges() - expect(input.type).toBe('text') - }); - }); \ No newline at end of file +import { Component, DebugElement, ChangeDetectorRef } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { ShowPasswordDirective } from '../directives'; + +@Component({ + template: ` + + + `, + imports: [ShowPasswordDirective], +}) +class TestComponent { + showPassword = false; +} + +describe('ShowPasswordDirective', () => { + let fixture: ComponentFixture; + let des: DebugElement[]; + let desAll: DebugElement[]; + let bareInput; + + beforeEach(() => { + fixture = TestBed.configureTestingModule({ + imports: [TestComponent], + }).createComponent(TestComponent); + + fixture.detectChanges(); + + des = fixture.debugElement.queryAll(By.directive(ShowPasswordDirective)); + + desAll = fixture.debugElement.queryAll(By.all()); + + bareInput = fixture.debugElement.query(By.css('input:not([abpShowPassword])')); + }); + + it('should have three input has ShowPasswordDirective elements', () => { + expect(des.length).toBe(3); + }); + + test.each([ + [0, 'text'], + [1, 'password'], + [2, 'text'], + [3, 'password'], + ])('%p. input type must be %p)', (index, inpType) => { + const inputType = desAll[index].nativeElement.type; + expect(inputType).toBe(inpType); + }); + + it('should toggle input type when showPassword changes', () => { + const input = des[2].nativeElement; + expect(input.type).toBe('password'); + + fixture.componentInstance.showPassword = true; + + const cdr = fixture.componentRef.injector.get(ChangeDetectorRef); + cdr.markForCheck(); + cdr.detectChanges(); + + expect(input.type).toBe('text'); + }); +}); diff --git a/npm/ng-packs/packages/core/src/lib/tests/sort.pipe.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/sort.pipe.spec.ts index 0d60ab0b21..7cc830990c 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/sort.pipe.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/sort.pipe.spec.ts @@ -1,4 +1,4 @@ -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; import { SortPipe } from '../pipes/sort.pipe'; describe('SortPipe', () => { diff --git a/npm/ng-packs/packages/core/src/lib/tests/stop-propagation.directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/stop-propagation.directive.spec.ts index f054601efa..8e0595c3c3 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/stop-propagation.directive.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/stop-propagation.directive.spec.ts @@ -1,12 +1,12 @@ -import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/jest'; +import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/vitest'; import { StopPropagationDirective } from '../directives/stop-propagation.directive'; describe('StopPropagationDirective', () => { let spectator: SpectatorDirective; let directive: StopPropagationDirective; let link: HTMLAnchorElement; - const childClickEventFn = jest.fn(() => null); - const parentClickEventFn = jest.fn(() => null); + const childClickEventFn = vi.fn(() => null); + const parentClickEventFn = vi.fn(() => null); const createDirective = createDirectiveFactory({ directive: StopPropagationDirective, }); @@ -28,12 +28,11 @@ describe('StopPropagationDirective', () => { expect(directive).toBeTruthy(); }); - test('should not call click event of parent when child element is clicked', done => { + test('should not call click event of parent when child element is clicked', () => { spectator.setHostInput({ parentClickEventFn, childClickEventFn }); spectator.click('a'); spectator.detectChanges(); expect(childClickEventFn).toHaveBeenCalled(); expect(parentClickEventFn).not.toHaveBeenCalled(); - done(); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/subscription.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/subscription.service.spec.ts index d0c8c4754f..191c5868c3 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/subscription.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/subscription.service.spec.ts @@ -14,8 +14,8 @@ describe('SubscriptionService', () => { describe('#addOne', () => { it('should subscribe to given observable with next and error functions and return the Subscription instance', () => { - const next = jest.fn(); - const error = jest.fn(); + const next = vi.fn(); + const error = vi.fn(); const subscription = service.addOne(of(null), next, error); expect(subscription).toBeInstanceOf(Subscription); expect(next).toHaveBeenCalledWith(null); @@ -24,7 +24,7 @@ describe('SubscriptionService', () => { }); it('should subscribe to given observable with observer and return the Subscription instance', () => { - const observer = { next: jest.fn(), complete: jest.fn() }; + const observer = { next: vi.fn(), complete: vi.fn() }; const subscription = service.addOne(of(null), observer); expect(subscription).toBeInstanceOf(Subscription); expect(observer.next).toHaveBeenCalledWith(null); diff --git a/npm/ng-packs/packages/core/src/test-setup.ts b/npm/ng-packs/packages/core/src/test-setup.ts index 13874ec714..1d4c608e96 100644 --- a/npm/ng-packs/packages/core/src/test-setup.ts +++ b/npm/ng-packs/packages/core/src/test-setup.ts @@ -1,7 +1,11 @@ -import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone'; -setupZoneTestEnv(); +import 'zone.js'; +import 'zone.js/testing'; +import { getTestBed } from '@angular/core/testing'; +import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing'; + +// Initialize Angular testing environment +getTestBed().initTestEnvironment(BrowserTestingModule, platformBrowserTesting()); -// Mock window.location for test environment Object.defineProperty(window, 'location', { value: { href: 'http://localhost:4200', diff --git a/npm/ng-packs/packages/core/tsconfig.lib.json b/npm/ng-packs/packages/core/tsconfig.lib.json index 22d2695db8..80ebc37002 100644 --- a/npm/ng-packs/packages/core/tsconfig.lib.json +++ b/npm/ng-packs/packages/core/tsconfig.lib.json @@ -10,6 +10,23 @@ "lib": ["dom", "es2020"], "useDefineForClassFields": false }, - "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], + "exclude": [ + "src/test-setup.ts", + "**/*.spec.ts", + "jest.config.ts", + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/test-setup.ts" + ], "include": ["**/*.ts"] } diff --git a/npm/ng-packs/packages/core/tsconfig.spec.json b/npm/ng-packs/packages/core/tsconfig.spec.json index be72f24e9b..8c496b20df 100644 --- a/npm/ng-packs/packages/core/tsconfig.spec.json +++ b/npm/ng-packs/packages/core/tsconfig.spec.json @@ -2,10 +2,22 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", - "types": ["jest", "node"], - "esModuleInterop": true + "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node", "vitest"] }, - "files": ["src/test-setup.ts"], - "include": ["**/*.ts"] + "include": [ + "vite.config.ts", + "vitest.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "files": ["src/test-setup.ts"] } diff --git a/npm/ng-packs/packages/core/vitest.config.mts b/npm/ng-packs/packages/core/vitest.config.mts new file mode 100644 index 0000000000..f3c41ed210 --- /dev/null +++ b/npm/ng-packs/packages/core/vitest.config.mts @@ -0,0 +1,22 @@ +import { defineConfig } from 'vitest/config'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/core', + plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + test: { + name: 'core', + watch: false, + globals: true, + environment: 'jsdom', + setupFiles: ['src/test-setup.ts'], + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/packages/core', + provider: 'v8' as const, + }, + }, +})); diff --git a/npm/ng-packs/packages/feature-management/.eslintrc.json b/npm/ng-packs/packages/feature-management/.eslintrc.json index 5e303d20bb..afa6cfec5f 100644 --- a/npm/ng-packs/packages/feature-management/.eslintrc.json +++ b/npm/ng-packs/packages/feature-management/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "ignorePatterns": ["!**/*", "**/vitest.config.*.timestamp*"], "overrides": [ { "files": ["*.ts"], diff --git a/npm/ng-packs/packages/feature-management/jest.config.ts b/npm/ng-packs/packages/feature-management/jest.config.ts index be424e36af..7740aa6d8a 100644 --- a/npm/ng-packs/packages/feature-management/jest.config.ts +++ b/npm/ng-packs/packages/feature-management/jest.config.ts @@ -1,4 +1,8 @@ /* eslint-disable */ +/** + * @deprecated use vitest instead of jest + * @see https://vitest.dev/guide/migration.html#jest + */ export default { displayName: 'feature-management', preset: '../../jest.preset.js', diff --git a/npm/ng-packs/packages/feature-management/package.json b/npm/ng-packs/packages/feature-management/package.json index 1b896680fc..8446aaa85d 100644 --- a/npm/ng-packs/packages/feature-management/package.json +++ b/npm/ng-packs/packages/feature-management/package.json @@ -1,13 +1,13 @@ { "name": "@abp/ng.feature-management", - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.theme.shared": "~10.1.0-rc.1", + "@abp/ng.theme.shared": "~10.1.0-rc.2", "tslib": "^2.0.0" }, "peerDependencies": { diff --git a/npm/ng-packs/packages/feature-management/project.json b/npm/ng-packs/packages/feature-management/project.json index 7712d1b67b..e34c0d6b16 100644 --- a/npm/ng-packs/packages/feature-management/project.json +++ b/npm/ng-packs/packages/feature-management/project.json @@ -23,16 +23,16 @@ }, "defaultConfiguration": "production" }, - "test": { - "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/packages/feature-management"], - "options": { - "jestConfig": "packages/feature-management/jest.config.ts" - } - }, "lint": { "executor": "@nx/eslint:lint", "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/vitest:test", + "outputs": ["{options.reportsDirectory}"], + "options": { + "reportsDirectory": "../../coverage/packages/feature-management" + } } } } diff --git a/npm/ng-packs/packages/feature-management/tsconfig.lib.json b/npm/ng-packs/packages/feature-management/tsconfig.lib.json index 22d2695db8..80ebc37002 100644 --- a/npm/ng-packs/packages/feature-management/tsconfig.lib.json +++ b/npm/ng-packs/packages/feature-management/tsconfig.lib.json @@ -10,6 +10,23 @@ "lib": ["dom", "es2020"], "useDefineForClassFields": false }, - "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], + "exclude": [ + "src/test-setup.ts", + "**/*.spec.ts", + "jest.config.ts", + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/test-setup.ts" + ], "include": ["**/*.ts"] } diff --git a/npm/ng-packs/packages/feature-management/tsconfig.spec.json b/npm/ng-packs/packages/feature-management/tsconfig.spec.json index 023d7d0b51..fc61345bb3 100644 --- a/npm/ng-packs/packages/feature-management/tsconfig.spec.json +++ b/npm/ng-packs/packages/feature-management/tsconfig.spec.json @@ -2,10 +2,22 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", - "types": ["jest", "node"], - "esModuleInterop": true + "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node", "vitest"] }, - "files": ["src/test-setup.ts"], - "include": ["**/*.spec.ts", "**/*.d.ts", "jest.config.ts"] + "include": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "files": ["src/test-setup.ts"] } diff --git a/npm/ng-packs/packages/feature-management/vitest.config.mts b/npm/ng-packs/packages/feature-management/vitest.config.mts new file mode 100644 index 0000000000..bfe50da685 --- /dev/null +++ b/npm/ng-packs/packages/feature-management/vitest.config.mts @@ -0,0 +1,21 @@ +import { defineConfig } from 'vitest/config'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/feature-management', + plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + test: { + name: 'feature-management', + watch: false, + globals: true, + environment: 'jsdom', + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/packages/feature-management', + provider: 'v8' as const, + }, + }, +})); diff --git a/npm/ng-packs/packages/generators/.eslintrc.json b/npm/ng-packs/packages/generators/.eslintrc.json index 99664e583f..18e0359cbb 100644 --- a/npm/ng-packs/packages/generators/.eslintrc.json +++ b/npm/ng-packs/packages/generators/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "ignorePatterns": ["!**/*", "**/vitest.config.*.timestamp*"], "overrides": [ { "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], diff --git a/npm/ng-packs/packages/generators/jest.config.ts b/npm/ng-packs/packages/generators/jest.config.ts index cd4c894fe7..0f5f3017ef 100644 --- a/npm/ng-packs/packages/generators/jest.config.ts +++ b/npm/ng-packs/packages/generators/jest.config.ts @@ -1,4 +1,8 @@ /* eslint-disable */ +/** + * @deprecated use vitest instead of jest + * @see https://vitest.dev/guide/migration.html#jest + */ export default { displayName: 'generators', preset: '../../jest.preset.js', diff --git a/npm/ng-packs/packages/generators/package.json b/npm/ng-packs/packages/generators/package.json index b70170a6bf..ff1932f9e0 100644 --- a/npm/ng-packs/packages/generators/package.json +++ b/npm/ng-packs/packages/generators/package.json @@ -1,6 +1,6 @@ { "name": "@abp/nx.generators", - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "homepage": "https://abp.io", "generators": "./generators.json", "type": "commonjs", diff --git a/npm/ng-packs/packages/generators/project.json b/npm/ng-packs/packages/generators/project.json index 5924f7caac..e0215aa306 100644 --- a/npm/ng-packs/packages/generators/project.json +++ b/npm/ng-packs/packages/generators/project.json @@ -46,10 +46,10 @@ "outputs": ["{options.outputFile}"] }, "test": { - "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "executor": "@nx/vitest:test", + "outputs": ["{options.reportsDirectory}"], "options": { - "jestConfig": "packages/generators/jest.config.ts" + "reportsDirectory": "../../coverage/packages/generators" } } } diff --git a/npm/ng-packs/packages/generators/src/generators/change-theme/generator.spec.ts b/npm/ng-packs/packages/generators/src/generators/change-theme/generator.spec.ts index 42c6753bbf..22900a606b 100644 --- a/npm/ng-packs/packages/generators/src/generators/change-theme/generator.spec.ts +++ b/npm/ng-packs/packages/generators/src/generators/change-theme/generator.spec.ts @@ -1,11 +1,11 @@ import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; -import { Tree, readProjectConfiguration } from '@nx/devkit'; +import { Tree } from '@nx/devkit'; import { changeThemeGenerator } from './generator'; import { ChangeThemeGeneratorSchema } from './schema'; -jest.mock('@nx/devkit/ngcli-adapter', () => ({ - wrapAngularDevkitSchematic: jest.fn(() => jest.fn()), +vi.mock('@nx/devkit/ngcli-adapter', () => ({ + wrapAngularDevkitSchematic: vi.fn(() => vi.fn()), })); describe('change-theme generator', () => { diff --git a/npm/ng-packs/packages/generators/tsconfig.lib.json b/npm/ng-packs/packages/generators/tsconfig.lib.json index 33eca2c2cd..b1cf8952fc 100644 --- a/npm/ng-packs/packages/generators/tsconfig.lib.json +++ b/npm/ng-packs/packages/generators/tsconfig.lib.json @@ -6,5 +6,19 @@ "types": ["node"] }, "include": ["src/**/*.ts"], - "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] + "exclude": [ + "jest.config.ts", + "src/**/*.spec.ts", + "src/**/*.test.ts", + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx" + ] } diff --git a/npm/ng-packs/packages/generators/tsconfig.spec.json b/npm/ng-packs/packages/generators/tsconfig.spec.json index f6d8ffcc9f..ba1ad41a90 100644 --- a/npm/ng-packs/packages/generators/tsconfig.spec.json +++ b/npm/ng-packs/packages/generators/tsconfig.spec.json @@ -2,8 +2,21 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", - "types": ["jest", "node"] + "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node", "vitest"] }, - "include": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts"] + "include": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ] } diff --git a/npm/ng-packs/packages/generators/vitest.config.mts b/npm/ng-packs/packages/generators/vitest.config.mts new file mode 100644 index 0000000000..299cea484b --- /dev/null +++ b/npm/ng-packs/packages/generators/vitest.config.mts @@ -0,0 +1,21 @@ +import { defineConfig } from 'vitest/config'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/generators', + plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + test: { + name: 'generators', + watch: false, + globals: true, + environment: 'jsdom', + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/packages/generators', + provider: 'v8' as const, + }, + }, +})); diff --git a/npm/ng-packs/packages/identity/.eslintrc.json b/npm/ng-packs/packages/identity/.eslintrc.json index 5e303d20bb..afa6cfec5f 100644 --- a/npm/ng-packs/packages/identity/.eslintrc.json +++ b/npm/ng-packs/packages/identity/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "ignorePatterns": ["!**/*", "**/vitest.config.*.timestamp*"], "overrides": [ { "files": ["*.ts"], diff --git a/npm/ng-packs/packages/identity/jest.config.ts b/npm/ng-packs/packages/identity/jest.config.ts index 469457531d..640c748717 100644 --- a/npm/ng-packs/packages/identity/jest.config.ts +++ b/npm/ng-packs/packages/identity/jest.config.ts @@ -1,4 +1,8 @@ /* eslint-disable */ +/** + * @deprecated use vitest instead of jest + * @see https://vitest.dev/guide/migration.html#jest + */ export default { displayName: 'identity', preset: '../../jest.preset.js', diff --git a/npm/ng-packs/packages/identity/package.json b/npm/ng-packs/packages/identity/package.json index d92e67e14e..a82f782f56 100644 --- a/npm/ng-packs/packages/identity/package.json +++ b/npm/ng-packs/packages/identity/package.json @@ -1,15 +1,15 @@ { "name": "@abp/ng.identity", - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.components": "~10.1.0-rc.1", - "@abp/ng.permission-management": "~10.1.0-rc.1", - "@abp/ng.theme.shared": "~10.1.0-rc.1", + "@abp/ng.components": "~10.1.0-rc.2", + "@abp/ng.permission-management": "~10.1.0-rc.2", + "@abp/ng.theme.shared": "~10.1.0-rc.2", "tslib": "^2.0.0" }, "peerDependencies": { diff --git a/npm/ng-packs/packages/identity/project.json b/npm/ng-packs/packages/identity/project.json index e67a92ba2a..cbded9c762 100644 --- a/npm/ng-packs/packages/identity/project.json +++ b/npm/ng-packs/packages/identity/project.json @@ -23,16 +23,16 @@ }, "defaultConfiguration": "production" }, - "test": { - "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/packages/identity"], - "options": { - "jestConfig": "packages/identity/jest.config.ts" - } - }, "lint": { "executor": "@nx/eslint:lint", "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/vitest:test", + "outputs": ["{options.reportsDirectory}"], + "options": { + "reportsDirectory": "../../coverage/packages/identity" + } } } } diff --git a/npm/ng-packs/packages/identity/tsconfig.lib.json b/npm/ng-packs/packages/identity/tsconfig.lib.json index 22d2695db8..80ebc37002 100644 --- a/npm/ng-packs/packages/identity/tsconfig.lib.json +++ b/npm/ng-packs/packages/identity/tsconfig.lib.json @@ -10,6 +10,23 @@ "lib": ["dom", "es2020"], "useDefineForClassFields": false }, - "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], + "exclude": [ + "src/test-setup.ts", + "**/*.spec.ts", + "jest.config.ts", + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/test-setup.ts" + ], "include": ["**/*.ts"] } diff --git a/npm/ng-packs/packages/identity/tsconfig.spec.json b/npm/ng-packs/packages/identity/tsconfig.spec.json index 023d7d0b51..fc61345bb3 100644 --- a/npm/ng-packs/packages/identity/tsconfig.spec.json +++ b/npm/ng-packs/packages/identity/tsconfig.spec.json @@ -2,10 +2,22 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", - "types": ["jest", "node"], - "esModuleInterop": true + "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node", "vitest"] }, - "files": ["src/test-setup.ts"], - "include": ["**/*.spec.ts", "**/*.d.ts", "jest.config.ts"] + "include": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "files": ["src/test-setup.ts"] } diff --git a/npm/ng-packs/packages/identity/vitest.config.mts b/npm/ng-packs/packages/identity/vitest.config.mts new file mode 100644 index 0000000000..89b2ebcb0e --- /dev/null +++ b/npm/ng-packs/packages/identity/vitest.config.mts @@ -0,0 +1,21 @@ +import { defineConfig } from 'vitest/config'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/identity', + plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + test: { + name: 'identity', + watch: false, + globals: true, + environment: 'jsdom', + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/packages/identity', + provider: 'v8' as const, + }, + }, +})); diff --git a/npm/ng-packs/packages/oauth/.eslintrc.json b/npm/ng-packs/packages/oauth/.eslintrc.json index 5e303d20bb..afa6cfec5f 100644 --- a/npm/ng-packs/packages/oauth/.eslintrc.json +++ b/npm/ng-packs/packages/oauth/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "ignorePatterns": ["!**/*", "**/vitest.config.*.timestamp*"], "overrides": [ { "files": ["*.ts"], diff --git a/npm/ng-packs/packages/oauth/jest.config.ts b/npm/ng-packs/packages/oauth/jest.config.ts index eb598713d5..1ff693025e 100644 --- a/npm/ng-packs/packages/oauth/jest.config.ts +++ b/npm/ng-packs/packages/oauth/jest.config.ts @@ -1,4 +1,8 @@ /* eslint-disable */ +/** + * @deprecated use vitest instead of jest + * @see https://vitest.dev/guide/migration.html#jest + */ export default { displayName: 'oauth', preset: '../../jest.preset.js', diff --git a/npm/ng-packs/packages/oauth/package.json b/npm/ng-packs/packages/oauth/package.json index 171090e65f..98bc02a772 100644 --- a/npm/ng-packs/packages/oauth/package.json +++ b/npm/ng-packs/packages/oauth/package.json @@ -1,14 +1,14 @@ { "name": "@abp/ng.oauth", - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.core": "~10.1.0-rc.1", - "@abp/utils": "~10.1.0-rc.1", + "@abp/ng.core": "~10.1.0-rc.2", + "@abp/utils": "~10.1.0-rc.2", "angular-oauth2-oidc": "^20.0.0", "just-clone": "^6.0.0", "just-compare": "^2.0.0", diff --git a/npm/ng-packs/packages/oauth/project.json b/npm/ng-packs/packages/oauth/project.json index 858ec5947c..a1948047b1 100644 --- a/npm/ng-packs/packages/oauth/project.json +++ b/npm/ng-packs/packages/oauth/project.json @@ -23,15 +23,15 @@ }, "defaultConfiguration": "production" }, + "lint": { + "executor": "@nx/eslint:lint" + }, "test": { - "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "executor": "@nx/vitest:test", + "outputs": ["{options.reportsDirectory}"], "options": { - "jestConfig": "packages/oauth/jest.config.ts" + "reportsDirectory": "../../coverage/packages/oauth" } - }, - "lint": { - "executor": "@nx/eslint:lint" } } } diff --git a/npm/ng-packs/packages/oauth/src/lib/tests/api.interceptor.spec.ts b/npm/ng-packs/packages/oauth/src/lib/tests/api.interceptor.spec.ts index 04a7652300..247b79b798 100644 --- a/npm/ng-packs/packages/oauth/src/lib/tests/api.interceptor.spec.ts +++ b/npm/ng-packs/packages/oauth/src/lib/tests/api.interceptor.spec.ts @@ -1,8 +1,8 @@ import { HttpRequest } from '@angular/common/http'; import { SpyObject } from '@ngneat/spectator'; -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; import { OAuthService } from 'angular-oauth2-oidc'; -import { Subject, timer } from 'rxjs'; +import { Subject } from 'rxjs'; import { HttpWaitService, SessionStateService, TENANT_KEY } from '@abp/ng.core'; import { OAuthApiInterceptor } from '../interceptors'; @@ -29,7 +29,7 @@ describe('ApiInterceptor', () => { httpWaitService = spectator.inject(HttpWaitService); }); - it('should add headers to http request', done => { + it('should add headers to http request', () => { oauthService.getAccessToken.andReturn('ey892mkwa8^2jk'); sessionState.getLanguage.andReturn('tr'); sessionState.getTenant.andReturn({ id: 'Volosoft', name: 'Volosoft' }); @@ -42,7 +42,6 @@ describe('ApiInterceptor', () => { expect(req.headers.get('Authorization')).toEqual('Bearer ey892mkwa8^2jk'); expect(req.headers.get('Accept-Language')).toEqual('tr'); expect(req.headers.get(testTenantKey)).toEqual('Volosoft'); - done(); return handleRes$; }, }; @@ -53,9 +52,9 @@ describe('ApiInterceptor', () => { handleRes$.complete(); }); - it('should call http wait services add request and delete request', done => { - const spyAddRequest = jest.spyOn(httpWaitService, 'addRequest'); - const spyDeleteRequest = jest.spyOn(httpWaitService, 'deleteRequest'); + it('should call http wait services add request and delete request', () => { + const spyAddRequest = vi.spyOn(httpWaitService, 'addRequest'); + const spyDeleteRequest = vi.spyOn(httpWaitService, 'deleteRequest'); const request = new HttpRequest('GET', 'https://abp.io'); const handleRes$ = new Subject(); @@ -71,10 +70,7 @@ describe('ApiInterceptor', () => { handleRes$.next(); handleRes$.complete(); - timer(0).subscribe(() => { - expect(spyAddRequest).toHaveBeenCalled(); - expect(spyDeleteRequest).toHaveBeenCalled(); - done(); - }); + expect(spyAddRequest).toHaveBeenCalled(); + expect(spyDeleteRequest).toHaveBeenCalled(); }); }); diff --git a/npm/ng-packs/packages/oauth/src/lib/tests/auth.guard.spec.ts b/npm/ng-packs/packages/oauth/src/lib/tests/auth.guard.spec.ts index 2b6603f39c..ccb4658999 100644 --- a/npm/ng-packs/packages/oauth/src/lib/tests/auth.guard.spec.ts +++ b/npm/ng-packs/packages/oauth/src/lib/tests/auth.guard.spec.ts @@ -1,4 +1,4 @@ -import { createServiceFactory, SpectatorService, createSpyObject } from '@ngneat/spectator/jest'; +import { createServiceFactory, SpectatorService, createSpyObject } from '@ngneat/spectator/vitest'; import { OAuthService } from 'angular-oauth2-oidc'; import { AbpOAuthGuard, abpOAuthGuard } from '../guards/oauth.guard'; import { AuthService } from '@abp/ng.core'; @@ -40,7 +40,7 @@ describe('AuthGuard', () => { it('should execute the navigateToLogin method of the authService', () => { const authService = spectator.inject(AuthService); spectator.inject(OAuthService).hasValidAccessToken.andReturn(false); - const navigateToLoginSpy = jest.spyOn(authService, 'navigateToLogin'); + const navigateToLoginSpy = vi.spyOn(authService, 'navigateToLogin'); expect(guard.canActivate(route, state)).toBe(false); expect(navigateToLoginSpy).toHaveBeenCalled(); diff --git a/npm/ng-packs/packages/oauth/src/lib/tests/initial-utils.spec.ts b/npm/ng-packs/packages/oauth/src/lib/tests/initial-utils.spec.ts index cd2dfbcac5..a8a4372817 100644 --- a/npm/ng-packs/packages/oauth/src/lib/tests/initial-utils.spec.ts +++ b/npm/ng-packs/packages/oauth/src/lib/tests/initial-utils.spec.ts @@ -1,6 +1,6 @@ import { Component, Injector } from '@angular/core'; -import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; -import { OAuthService } from 'angular-oauth2-oidc'; +import { createComponentFactory, Spectator } from '@ngneat/spectator/vitest'; +import { OAuthService, OAuthStorage } from 'angular-oauth2-oidc'; import { CORE_OPTIONS, @@ -42,6 +42,15 @@ describe('InitialUtils', () => { skipGetAppConfiguration: false, }, }, + { + provide: OAuthStorage, + useValue: { + getItem: vi.fn(), + setItem: vi.fn(), + removeItem: vi.fn(), + clear: vi.fn(), + }, + }, ], }); @@ -53,8 +62,8 @@ describe('InitialUtils', () => { let clearOAuthStorageSpy; beforeEach(() => { injector = spectator.inject(Injector); - injectorSpy = jest.spyOn(injector, 'get'); - clearOAuthStorageSpy = jest.spyOn(clearOAuthStorageDefault, 'clearOAuthStorage'); + injectorSpy = vi.spyOn(injector, 'get'); + clearOAuthStorageSpy = vi.spyOn(clearOAuthStorageDefault, 'clearOAuthStorage'); clearOAuthStorageSpy.mockReset(); }); diff --git a/npm/ng-packs/packages/oauth/src/lib/tests/oauth-error-filter.service.spec.ts b/npm/ng-packs/packages/oauth/src/lib/tests/oauth-error-filter.service.spec.ts index c8af0f934d..a112e847ec 100644 --- a/npm/ng-packs/packages/oauth/src/lib/tests/oauth-error-filter.service.spec.ts +++ b/npm/ng-packs/packages/oauth/src/lib/tests/oauth-error-filter.service.spec.ts @@ -1,14 +1,13 @@ -import { createServiceFactory, SpectatorService } from '@ngneat/spectator'; -import { OAuthErrorFilterService } from '../services'; -import { AuthErrorEvent, AuthErrorFilter } from '@abp/ng.core'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; import { OAuthErrorEvent } from 'angular-oauth2-oidc'; +import { AuthErrorEvent, AuthErrorFilter } from '@abp/ng.core'; +import { OAuthErrorFilterService } from '../services'; const ids = { firstFilter: 'firstFilter', secondFilter: 'secondFilter', }; -type Reason = object & { error: { grant_type: string | undefined; }; }; - +type Reason = object & { error: { grant_type: string | undefined } }; describe('AuthService', () => { let spectator: SpectatorService; @@ -37,11 +36,11 @@ describe('AuthService', () => { const { error: { grant_type }, } = (reason || {}); - + return !!grant_type && grant_type === ids.firstFilter; }, }; - + secondFilter = { id: ids.secondFilter, executable: true, @@ -50,7 +49,7 @@ describe('AuthService', () => { const { error: { grant_type }, } = (reason || {}); - + return !!grant_type && grant_type === ids.secondFilter; }, }; @@ -118,4 +117,4 @@ describe('AuthService', () => { expect(oAuthErrorFilterService.run(event)).toBe(false); }); -}); \ No newline at end of file +}); diff --git a/npm/ng-packs/packages/oauth/src/lib/tests/remember-me.service.spec.ts b/npm/ng-packs/packages/oauth/src/lib/tests/remember-me.service.spec.ts index 0ef7116e92..c6d7b81e01 100644 --- a/npm/ng-packs/packages/oauth/src/lib/tests/remember-me.service.spec.ts +++ b/npm/ng-packs/packages/oauth/src/lib/tests/remember-me.service.spec.ts @@ -1,8 +1,6 @@ -import { SpectatorService, SpyObject, createServiceFactory } from "@ngneat/spectator/jest"; -import { RememberMeService } from "../services/remember-me.service"; -import { AbpLocalStorageService } from "@abp/ng.core"; - - +import { SpectatorService, SpyObject, createServiceFactory } from '@ngneat/spectator/vitest'; +import { AbpLocalStorageService } from '@abp/ng.core'; +import { RememberMeService } from '../services/remember-me.service'; describe('RememberMeService', () => { const key = 'remember_me'; @@ -12,10 +10,9 @@ describe('RememberMeService', () => { const createService = createServiceFactory({ service: RememberMeService, - mocks: [AbpLocalStorageService] + mocks: [AbpLocalStorageService], }); - beforeEach(() => { spectator = createService(); rememberMeService = spectator.inject(RememberMeService); @@ -55,17 +52,16 @@ describe('RememberMeService', () => { }); it('should return true when parsed token is setted to true', () => { - const data = { "remember_me": "True" }; + const data = { remember_me: 'True' }; const base64_encoded = btoa(JSON.stringify(data)); - const tokenWithValueTrue = "random." + base64_encoded + ".random"; + const tokenWithValueTrue = 'random.' + base64_encoded + '.random'; expect(rememberMeService.getFromToken(tokenWithValueTrue)).toBe(true); }); it('should return false when value is not setted(undefined)', () => { const data = {}; const base64_encoded = btoa(JSON.stringify(data)); - const tokenWithValueTrue = "random." + base64_encoded + ".random"; + const tokenWithValueTrue = 'random.' + base64_encoded + '.random'; expect(rememberMeService.getFromToken(tokenWithValueTrue)).toBe(false); }); - -}); \ No newline at end of file +}); diff --git a/npm/ng-packs/packages/oauth/src/lib/utils/check-access-token.ts b/npm/ng-packs/packages/oauth/src/lib/utils/check-access-token.ts index 84457c4c3c..38111dc3e9 100644 --- a/npm/ng-packs/packages/oauth/src/lib/utils/check-access-token.ts +++ b/npm/ng-packs/packages/oauth/src/lib/utils/check-access-token.ts @@ -7,6 +7,6 @@ export const checkAccessToken: CheckAuthenticationStateFn = function (injector: const configState = injector.get(ConfigStateService); const oAuth = injector.get(OAuthService); if (oAuth.hasValidAccessToken() && !configState.getDeep('currentUser.id')) { - clearOAuthStorage(this.injector); + clearOAuthStorage(injector); } }; diff --git a/npm/ng-packs/packages/oauth/src/test-setup.ts b/npm/ng-packs/packages/oauth/src/test-setup.ts index 4555f138a7..b72ff2d424 100644 --- a/npm/ng-packs/packages/oauth/src/test-setup.ts +++ b/npm/ng-packs/packages/oauth/src/test-setup.ts @@ -1,2 +1,19 @@ -import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone'; -setupZoneTestEnv(); +import 'zone.js'; +import 'zone.js/testing'; +import { getTestBed } from '@angular/core/testing'; +import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing'; + +// Initialize Angular testing environment +getTestBed().initTestEnvironment(BrowserTestingModule, platformBrowserTesting()); + +// Mock window.location for test environment +Object.defineProperty(window, 'location', { + value: { + href: 'http://localhost:4200', + origin: 'http://localhost:4200', + pathname: '/', + search: '', + hash: '', + }, + writable: true, +}); diff --git a/npm/ng-packs/packages/oauth/tsconfig.lib.json b/npm/ng-packs/packages/oauth/tsconfig.lib.json index 22d2695db8..80ebc37002 100644 --- a/npm/ng-packs/packages/oauth/tsconfig.lib.json +++ b/npm/ng-packs/packages/oauth/tsconfig.lib.json @@ -10,6 +10,23 @@ "lib": ["dom", "es2020"], "useDefineForClassFields": false }, - "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], + "exclude": [ + "src/test-setup.ts", + "**/*.spec.ts", + "jest.config.ts", + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/test-setup.ts" + ], "include": ["**/*.ts"] } diff --git a/npm/ng-packs/packages/oauth/tsconfig.spec.json b/npm/ng-packs/packages/oauth/tsconfig.spec.json index be72f24e9b..fc61345bb3 100644 --- a/npm/ng-packs/packages/oauth/tsconfig.spec.json +++ b/npm/ng-packs/packages/oauth/tsconfig.spec.json @@ -2,10 +2,22 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", - "types": ["jest", "node"], - "esModuleInterop": true + "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node", "vitest"] }, - "files": ["src/test-setup.ts"], - "include": ["**/*.ts"] + "include": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "files": ["src/test-setup.ts"] } diff --git a/npm/ng-packs/packages/oauth/vitest.config.mts b/npm/ng-packs/packages/oauth/vitest.config.mts new file mode 100644 index 0000000000..292f5376e2 --- /dev/null +++ b/npm/ng-packs/packages/oauth/vitest.config.mts @@ -0,0 +1,22 @@ +import { defineConfig } from 'vitest/config'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/oauth', + plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + test: { + name: 'oauth', + watch: false, + globals: true, + environment: 'jsdom', + setupFiles: ['src/test-setup.ts'], + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/packages/oauth', + provider: 'v8' as const, + }, + }, +})); diff --git a/npm/ng-packs/packages/permission-management/.eslintrc.json b/npm/ng-packs/packages/permission-management/.eslintrc.json index 5e303d20bb..afa6cfec5f 100644 --- a/npm/ng-packs/packages/permission-management/.eslintrc.json +++ b/npm/ng-packs/packages/permission-management/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "ignorePatterns": ["!**/*", "**/vitest.config.*.timestamp*"], "overrides": [ { "files": ["*.ts"], diff --git a/npm/ng-packs/packages/permission-management/jest.config.ts b/npm/ng-packs/packages/permission-management/jest.config.ts index 78cb7d6c8f..bc3f3993e8 100644 --- a/npm/ng-packs/packages/permission-management/jest.config.ts +++ b/npm/ng-packs/packages/permission-management/jest.config.ts @@ -1,4 +1,8 @@ /* eslint-disable */ +/** + * @deprecated use vitest instead of jest + * @see https://vitest.dev/guide/migration.html#jest + */ export default { displayName: 'permission-management', preset: '../../jest.preset.js', diff --git a/npm/ng-packs/packages/permission-management/package.json b/npm/ng-packs/packages/permission-management/package.json index 6bfcaa77e7..264a8f8a87 100644 --- a/npm/ng-packs/packages/permission-management/package.json +++ b/npm/ng-packs/packages/permission-management/package.json @@ -1,13 +1,13 @@ { "name": "@abp/ng.permission-management", - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.theme.shared": "~10.1.0-rc.1", + "@abp/ng.theme.shared": "~10.1.0-rc.2", "tslib": "^2.0.0" }, "peerDependencies": { diff --git a/npm/ng-packs/packages/permission-management/project.json b/npm/ng-packs/packages/permission-management/project.json index 3e3287256d..ddd8ad11af 100644 --- a/npm/ng-packs/packages/permission-management/project.json +++ b/npm/ng-packs/packages/permission-management/project.json @@ -23,16 +23,16 @@ }, "defaultConfiguration": "production" }, - "test": { - "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/packages/permission-management"], - "options": { - "jestConfig": "packages/permission-management/jest.config.ts" - } - }, "lint": { "executor": "@nx/eslint:lint", "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/vitest:test", + "outputs": ["{options.reportsDirectory}"], + "options": { + "reportsDirectory": "../../coverage/packages/permission-management" + } } } } diff --git a/npm/ng-packs/packages/permission-management/tsconfig.lib.json b/npm/ng-packs/packages/permission-management/tsconfig.lib.json index 22d2695db8..80ebc37002 100644 --- a/npm/ng-packs/packages/permission-management/tsconfig.lib.json +++ b/npm/ng-packs/packages/permission-management/tsconfig.lib.json @@ -10,6 +10,23 @@ "lib": ["dom", "es2020"], "useDefineForClassFields": false }, - "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], + "exclude": [ + "src/test-setup.ts", + "**/*.spec.ts", + "jest.config.ts", + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/test-setup.ts" + ], "include": ["**/*.ts"] } diff --git a/npm/ng-packs/packages/permission-management/tsconfig.spec.json b/npm/ng-packs/packages/permission-management/tsconfig.spec.json index 023d7d0b51..fc61345bb3 100644 --- a/npm/ng-packs/packages/permission-management/tsconfig.spec.json +++ b/npm/ng-packs/packages/permission-management/tsconfig.spec.json @@ -2,10 +2,22 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", - "types": ["jest", "node"], - "esModuleInterop": true + "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node", "vitest"] }, - "files": ["src/test-setup.ts"], - "include": ["**/*.spec.ts", "**/*.d.ts", "jest.config.ts"] + "include": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "files": ["src/test-setup.ts"] } diff --git a/npm/ng-packs/packages/permission-management/vitest.config.mts b/npm/ng-packs/packages/permission-management/vitest.config.mts new file mode 100644 index 0000000000..7471e1446f --- /dev/null +++ b/npm/ng-packs/packages/permission-management/vitest.config.mts @@ -0,0 +1,21 @@ +import { defineConfig } from 'vitest/config'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/permission-management', + plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + test: { + name: 'permission-management', + watch: false, + globals: true, + environment: 'jsdom', + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/packages/permission-management', + provider: 'v8' as const, + }, + }, +})); diff --git a/npm/ng-packs/packages/schematics/.eslintrc.json b/npm/ng-packs/packages/schematics/.eslintrc.json index a291bd52a8..12efb6a283 100644 --- a/npm/ng-packs/packages/schematics/.eslintrc.json +++ b/npm/ng-packs/packages/schematics/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "ignorePatterns": ["!**/*", "**/vitest.config.*.timestamp*"], "overrides": [ { "files": ["*.ts"], diff --git a/npm/ng-packs/packages/schematics/jest.config.ts b/npm/ng-packs/packages/schematics/jest.config.ts index 70fa6f0001..03eb6a87a7 100644 --- a/npm/ng-packs/packages/schematics/jest.config.ts +++ b/npm/ng-packs/packages/schematics/jest.config.ts @@ -1,4 +1,8 @@ /* eslint-disable */ +/** + * @deprecated use vitest instead of jest + * @see https://vitest.dev/guide/migration.html#jest + */ export default { displayName: 'schematics', preset: '../../jest.preset.js', diff --git a/npm/ng-packs/packages/schematics/package.json b/npm/ng-packs/packages/schematics/package.json index 901db53a70..199be73f20 100644 --- a/npm/ng-packs/packages/schematics/package.json +++ b/npm/ng-packs/packages/schematics/package.json @@ -1,6 +1,6 @@ { "name": "@abp/ng.schematics", - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "author": "", "schematics": "./collection.json", "dependencies": { diff --git a/npm/ng-packs/packages/schematics/project.json b/npm/ng-packs/packages/schematics/project.json index 74a01e9f03..d12fff2738 100644 --- a/npm/ng-packs/packages/schematics/project.json +++ b/npm/ng-packs/packages/schematics/project.json @@ -6,16 +6,16 @@ "prefix": "abp", "tags": [], "targets": { - "test": { - "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/packages/schematics"], - "options": { - "jestConfig": "packages/schematics/jest.config.ts" - } - }, "lint": { "executor": "@nx/eslint:lint", "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/vitest:test", + "outputs": ["{options.reportsDirectory}"], + "options": { + "reportsDirectory": "../../coverage/packages/schematics" + } } } } diff --git a/npm/ng-packs/packages/schematics/src/test-setup.ts b/npm/ng-packs/packages/schematics/src/test-setup.ts deleted file mode 100644 index 1100b3e8a6..0000000000 --- a/npm/ng-packs/packages/schematics/src/test-setup.ts +++ /dev/null @@ -1 +0,0 @@ -import 'jest-preset-angular/setup-jest'; diff --git a/npm/ng-packs/packages/schematics/src/tests/parse-generic-type.spec.ts b/npm/ng-packs/packages/schematics/src/tests/parse-generic-type.spec.ts index 400fcdf6eb..4bf520e108 100644 --- a/npm/ng-packs/packages/schematics/src/tests/parse-generic-type.spec.ts +++ b/npm/ng-packs/packages/schematics/src/tests/parse-generic-type.spec.ts @@ -1,7 +1,6 @@ +import { expect, describe, it, test } from 'vitest'; import { parseBaseTypeWithGenericTypes } from '../utils/model'; -import { parseGenerics } from '../utils/tree'; - -import {test} from '@jest/globals'; +import { parseGenerics } from '../utils/tree'; const cases: Array<[string, string[]]> = [ [ @@ -17,7 +16,7 @@ const cases: Array<[string, string[]]> = [ [ 'Volo.Abp.Application.Dtos.AuditedEntityWithUserDto', 'string', - 'Volo.Abp.Identity.IdentityUserDto' + 'Volo.Abp.Identity.IdentityUserDto', ], ], [ @@ -39,41 +38,38 @@ const cases: Array<[string, string[]]> = [ 'System.String', ], ], - [ - 'AuditedEntityWithUserDto', - ['AuditedEntityWithUserDto'], - ], + ['AuditedEntityWithUserDto', ['AuditedEntityWithUserDto']], ]; test.each(cases)('should parse %s', (inputStr, expected) => { - const parsed = parseBaseTypeWithGenericTypes(inputStr); - expect(parsed).toEqual(expected); -}) + const parsed = parseBaseTypeWithGenericTypes(inputStr); + expect(parsed).toEqual(expected); +}); describe('parseGenerics', () => { - - it('should work with simple type', function() { + it('should work with simple type', function () { const node = parseGenerics('System.String'); expect(node.data).toEqual('System.String'); expect(node.index).toBe(0); expect(node.parent).toBe(null); }); - it('should work with simple Array type', function() { + it('should work with simple Array type', function () { const node = parseGenerics('System.String[]'); expect(node.data).toEqual('System.String[]'); expect(node.index).toBe(0); expect(node.parent).toBe(null); }); - it('should work with simple', function() { + it('should work with simple', function () { const node = parseGenerics('Volo.Abp.Application.Dtos.AuditedEntityWithUserDto'); expect(node.data).toEqual('Volo.Abp.Application.Dtos.AuditedEntityWithUserDto'); expect(node.index).toBe(0); expect(node.parent).toBe(null); }); - it('should work with `Volo.Abp.Application.Dtos.AuditedEntityWithUserDto`', function() { - const type = 'Volo.Abp.Application.Dtos.AuditedEntityWithUserDto'; + it('should work with `Volo.Abp.Application.Dtos.AuditedEntityWithUserDto`', function () { + const type = + 'Volo.Abp.Application.Dtos.AuditedEntityWithUserDto'; const node = parseGenerics(type); expect(node.data).toEqual('Volo.Abp.Application.Dtos.AuditedEntityWithUserDto'); @@ -83,37 +79,37 @@ describe('parseGenerics', () => { expect(child.parent).toBe(node); }); - - it('should work with `Volo.Abp.Application.Dtos.AuditedEntityWithUserDto`', function() { - const type = 'Volo.Abp.Application.Dtos.AuditedEntityWithUserDto'; + it('should work with `Volo.Abp.Application.Dtos.AuditedEntityWithUserDto`', function () { + const type = + 'Volo.Abp.Application.Dtos.AuditedEntityWithUserDto'; const node = parseGenerics(type); expect(node.data).toEqual('Volo.Abp.Application.Dtos.AuditedEntityWithUserDto'); expect(node.children.length).toBe(2); expect(node.children[0].data).toEqual('System.string'); - expect(node.children[0].index).toBe(0) + expect(node.children[0].index).toBe(0); expect(node.children[1].data).toEqual('Volo.Abp.Identity.IdentityUserDto'); expect(node.children[1].index).toBe(1); - }); - it('should Volo.Abp.Application.Dtos.AuditedEntityWithUserDto>', function() { - const type = 'Volo.Abp.Application.Dtos.AuditedEntityWithUserDto>'; + it('should Volo.Abp.Application.Dtos.AuditedEntityWithUserDto>', function () { + const type = + 'Volo.Abp.Application.Dtos.AuditedEntityWithUserDto>'; const node = parseGenerics(type); expect(node.data).toEqual('Volo.Abp.Application.Dtos.AuditedEntityWithUserDto'); expect(node.children.length).toBe(2); expect(node.children[0].data).toEqual('System.string'); - expect((node.children[0]).parent).toBe(node); + expect(node.children[0].parent).toBe(node); expect(node.children[1].data).toEqual('Volo.Abp.Identity.IdentityUserDto'); expect(node.children[1].children.length).toBe(1); expect(node.children[1].children[0].data).toEqual('System.Int'); - expect(node.children[1].children[0].parent).toBe(node.children[1]) - expect(node.children[1].children[0].index).toBe(0) - + expect(node.children[1].children[0].parent).toBe(node.children[1]); + expect(node.children[1].children[0].index).toBe(0); }); - it('should Volo.Abp.Application.Dtos.AuditedEntityWithUserDto,System.string>', function() { - const type = 'Volo.Abp.Application.Dtos.AuditedEntityWithUserDto,System.string>'; + it('should Volo.Abp.Application.Dtos.AuditedEntityWithUserDto,System.string>', function () { + const type = + 'Volo.Abp.Application.Dtos.AuditedEntityWithUserDto,System.string>'; const node = parseGenerics(type); expect(node.data).toEqual('Volo.Abp.Application.Dtos.AuditedEntityWithUserDto'); expect(node.children.length).toBe(2); @@ -121,9 +117,5 @@ describe('parseGenerics', () => { expect(node.children[0].children.length).toBe(1); expect(node.children[0].children[0].data).toEqual('System.Int'); expect(node.children[1].data).toEqual('System.string'); - }); }); - - - diff --git a/npm/ng-packs/packages/schematics/tsconfig.spec.json b/npm/ng-packs/packages/schematics/tsconfig.spec.json index a42c4b02fb..fc61345bb3 100644 --- a/npm/ng-packs/packages/schematics/tsconfig.spec.json +++ b/npm/ng-packs/packages/schematics/tsconfig.spec.json @@ -2,10 +2,22 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", - "types": ["jest", "node"], - "esModuleInterop": true + "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node", "vitest"] }, - "files": ["src/test-setup.ts"], - "include": ["**/*.spec.ts", "**/*.d.ts"] + "include": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "files": ["src/test-setup.ts"] } diff --git a/npm/ng-packs/packages/schematics/vitest.config.mts b/npm/ng-packs/packages/schematics/vitest.config.mts new file mode 100644 index 0000000000..6398ad426d --- /dev/null +++ b/npm/ng-packs/packages/schematics/vitest.config.mts @@ -0,0 +1,21 @@ +import { defineConfig } from 'vitest/config'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/schematics', + plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + test: { + name: 'schematics', + watch: false, + globals: true, + environment: 'jsdom', + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/packages/schematics', + provider: 'v8' as const, + }, + }, +})); diff --git a/npm/ng-packs/packages/setting-management/.eslintrc.json b/npm/ng-packs/packages/setting-management/.eslintrc.json index 5e303d20bb..afa6cfec5f 100644 --- a/npm/ng-packs/packages/setting-management/.eslintrc.json +++ b/npm/ng-packs/packages/setting-management/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "ignorePatterns": ["!**/*", "**/vitest.config.*.timestamp*"], "overrides": [ { "files": ["*.ts"], diff --git a/npm/ng-packs/packages/setting-management/jest.config.ts b/npm/ng-packs/packages/setting-management/jest.config.ts index 17d04089d0..be1cc254ed 100644 --- a/npm/ng-packs/packages/setting-management/jest.config.ts +++ b/npm/ng-packs/packages/setting-management/jest.config.ts @@ -1,4 +1,8 @@ /* eslint-disable */ +/** + * @deprecated use vitest instead of jest + * @see https://vitest.dev/guide/migration.html#jest + */ export default { displayName: 'setting-management', preset: '../../jest.preset.js', diff --git a/npm/ng-packs/packages/setting-management/package.json b/npm/ng-packs/packages/setting-management/package.json index 03d471f0a8..7961dca68a 100644 --- a/npm/ng-packs/packages/setting-management/package.json +++ b/npm/ng-packs/packages/setting-management/package.json @@ -1,14 +1,14 @@ { "name": "@abp/ng.setting-management", - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.components": "~10.1.0-rc.1", - "@abp/ng.theme.shared": "~10.1.0-rc.1", + "@abp/ng.components": "~10.1.0-rc.2", + "@abp/ng.theme.shared": "~10.1.0-rc.2", "tslib": "^2.0.0" }, "peerDependencies": { diff --git a/npm/ng-packs/packages/setting-management/project.json b/npm/ng-packs/packages/setting-management/project.json index 57f72234fb..fa07efdfc3 100644 --- a/npm/ng-packs/packages/setting-management/project.json +++ b/npm/ng-packs/packages/setting-management/project.json @@ -23,16 +23,16 @@ }, "defaultConfiguration": "production" }, - "test": { - "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/packages/setting-management"], - "options": { - "jestConfig": "packages/setting-management/jest.config.ts" - } - }, "lint": { "executor": "@nx/eslint:lint", "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/vitest:test", + "outputs": ["{options.reportsDirectory}"], + "options": { + "reportsDirectory": "../../coverage/packages/setting-management" + } } } } diff --git a/npm/ng-packs/packages/setting-management/tsconfig.lib.json b/npm/ng-packs/packages/setting-management/tsconfig.lib.json index 22d2695db8..80ebc37002 100644 --- a/npm/ng-packs/packages/setting-management/tsconfig.lib.json +++ b/npm/ng-packs/packages/setting-management/tsconfig.lib.json @@ -10,6 +10,23 @@ "lib": ["dom", "es2020"], "useDefineForClassFields": false }, - "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], + "exclude": [ + "src/test-setup.ts", + "**/*.spec.ts", + "jest.config.ts", + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/test-setup.ts" + ], "include": ["**/*.ts"] } diff --git a/npm/ng-packs/packages/setting-management/tsconfig.spec.json b/npm/ng-packs/packages/setting-management/tsconfig.spec.json index 023d7d0b51..fc61345bb3 100644 --- a/npm/ng-packs/packages/setting-management/tsconfig.spec.json +++ b/npm/ng-packs/packages/setting-management/tsconfig.spec.json @@ -2,10 +2,22 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", - "types": ["jest", "node"], - "esModuleInterop": true + "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node", "vitest"] }, - "files": ["src/test-setup.ts"], - "include": ["**/*.spec.ts", "**/*.d.ts", "jest.config.ts"] + "include": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "files": ["src/test-setup.ts"] } diff --git a/npm/ng-packs/packages/setting-management/vitest.config.mts b/npm/ng-packs/packages/setting-management/vitest.config.mts new file mode 100644 index 0000000000..d1670d87fe --- /dev/null +++ b/npm/ng-packs/packages/setting-management/vitest.config.mts @@ -0,0 +1,21 @@ +import { defineConfig } from 'vitest/config'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/setting-management', + plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + test: { + name: 'setting-management', + watch: false, + globals: true, + environment: 'jsdom', + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/packages/setting-management', + provider: 'v8' as const, + }, + }, +})); diff --git a/npm/ng-packs/packages/tenant-management/.eslintrc.json b/npm/ng-packs/packages/tenant-management/.eslintrc.json index 5e303d20bb..afa6cfec5f 100644 --- a/npm/ng-packs/packages/tenant-management/.eslintrc.json +++ b/npm/ng-packs/packages/tenant-management/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "ignorePatterns": ["!**/*", "**/vitest.config.*.timestamp*"], "overrides": [ { "files": ["*.ts"], diff --git a/npm/ng-packs/packages/tenant-management/jest.config.ts b/npm/ng-packs/packages/tenant-management/jest.config.ts index e69152e9ab..a5d63bbaaf 100644 --- a/npm/ng-packs/packages/tenant-management/jest.config.ts +++ b/npm/ng-packs/packages/tenant-management/jest.config.ts @@ -1,4 +1,8 @@ /* eslint-disable */ +/** + * @deprecated use vitest instead of jest + * @see https://vitest.dev/guide/migration.html#jest + */ export default { displayName: 'tenant-management', preset: '../../jest.preset.js', diff --git a/npm/ng-packs/packages/tenant-management/package.json b/npm/ng-packs/packages/tenant-management/package.json index e92e12af27..1ced045022 100644 --- a/npm/ng-packs/packages/tenant-management/package.json +++ b/npm/ng-packs/packages/tenant-management/package.json @@ -1,14 +1,14 @@ { "name": "@abp/ng.tenant-management", - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.feature-management": "~10.1.0-rc.1", - "@abp/ng.theme.shared": "~10.1.0-rc.1", + "@abp/ng.feature-management": "~10.1.0-rc.2", + "@abp/ng.theme.shared": "~10.1.0-rc.2", "tslib": "^2.0.0" }, "publishConfig": { diff --git a/npm/ng-packs/packages/tenant-management/project.json b/npm/ng-packs/packages/tenant-management/project.json index 2394793380..9bbd10d632 100644 --- a/npm/ng-packs/packages/tenant-management/project.json +++ b/npm/ng-packs/packages/tenant-management/project.json @@ -23,16 +23,16 @@ }, "defaultConfiguration": "production" }, - "test": { - "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/packages/tenant-management"], - "options": { - "jestConfig": "packages/tenant-management/jest.config.ts" - } - }, "lint": { "executor": "@nx/eslint:lint", "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/vitest:test", + "outputs": ["{options.reportsDirectory}"], + "options": { + "reportsDirectory": "../../coverage/packages/tenant-management" + } } } } diff --git a/npm/ng-packs/packages/tenant-management/tsconfig.lib.json b/npm/ng-packs/packages/tenant-management/tsconfig.lib.json index 22d2695db8..80ebc37002 100644 --- a/npm/ng-packs/packages/tenant-management/tsconfig.lib.json +++ b/npm/ng-packs/packages/tenant-management/tsconfig.lib.json @@ -10,6 +10,23 @@ "lib": ["dom", "es2020"], "useDefineForClassFields": false }, - "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], + "exclude": [ + "src/test-setup.ts", + "**/*.spec.ts", + "jest.config.ts", + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/test-setup.ts" + ], "include": ["**/*.ts"] } diff --git a/npm/ng-packs/packages/tenant-management/tsconfig.spec.json b/npm/ng-packs/packages/tenant-management/tsconfig.spec.json index 023d7d0b51..fc61345bb3 100644 --- a/npm/ng-packs/packages/tenant-management/tsconfig.spec.json +++ b/npm/ng-packs/packages/tenant-management/tsconfig.spec.json @@ -2,10 +2,22 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", - "types": ["jest", "node"], - "esModuleInterop": true + "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node", "vitest"] }, - "files": ["src/test-setup.ts"], - "include": ["**/*.spec.ts", "**/*.d.ts", "jest.config.ts"] + "include": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "files": ["src/test-setup.ts"] } diff --git a/npm/ng-packs/packages/tenant-management/vitest.config.mts b/npm/ng-packs/packages/tenant-management/vitest.config.mts new file mode 100644 index 0000000000..9125d40292 --- /dev/null +++ b/npm/ng-packs/packages/tenant-management/vitest.config.mts @@ -0,0 +1,21 @@ +import { defineConfig } from 'vitest/config'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/tenant-management', + plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + test: { + name: 'tenant-management', + watch: false, + globals: true, + environment: 'jsdom', + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/packages/tenant-management', + provider: 'v8' as const, + }, + }, +})); diff --git a/npm/ng-packs/packages/theme-basic/.eslintrc.json b/npm/ng-packs/packages/theme-basic/.eslintrc.json index 5e303d20bb..afa6cfec5f 100644 --- a/npm/ng-packs/packages/theme-basic/.eslintrc.json +++ b/npm/ng-packs/packages/theme-basic/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "ignorePatterns": ["!**/*", "**/vitest.config.*.timestamp*"], "overrides": [ { "files": ["*.ts"], diff --git a/npm/ng-packs/packages/theme-basic/jest.config.ts b/npm/ng-packs/packages/theme-basic/jest.config.ts index 5e30d9abd6..2c6efb6a7e 100644 --- a/npm/ng-packs/packages/theme-basic/jest.config.ts +++ b/npm/ng-packs/packages/theme-basic/jest.config.ts @@ -1,4 +1,8 @@ /* eslint-disable */ +/** + * @deprecated use vitest instead of jest + * @see https://vitest.dev/guide/migration.html#jest + */ export default { displayName: 'theme-basic', preset: '../../jest.preset.js', diff --git a/npm/ng-packs/packages/theme-basic/package.json b/npm/ng-packs/packages/theme-basic/package.json index 796531eb6a..e6e08aa95f 100644 --- a/npm/ng-packs/packages/theme-basic/package.json +++ b/npm/ng-packs/packages/theme-basic/package.json @@ -1,14 +1,14 @@ { "name": "@abp/ng.theme.basic", - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.account.core": "~10.1.0-rc.1", - "@abp/ng.theme.shared": "~10.1.0-rc.1", + "@abp/ng.account.core": "~10.1.0-rc.2", + "@abp/ng.theme.shared": "~10.1.0-rc.2", "tslib": "^2.0.0" }, "publishConfig": { diff --git a/npm/ng-packs/packages/theme-basic/project.json b/npm/ng-packs/packages/theme-basic/project.json index be0464cc18..ac155e0fe7 100644 --- a/npm/ng-packs/packages/theme-basic/project.json +++ b/npm/ng-packs/packages/theme-basic/project.json @@ -23,16 +23,16 @@ }, "defaultConfiguration": "production" }, - "test": { - "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/packages/theme-basic"], - "options": { - "jestConfig": "packages/theme-basic/jest.config.ts" - } - }, "lint": { "executor": "@nx/eslint:lint", "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/vitest:test", + "outputs": ["{options.reportsDirectory}"], + "options": { + "reportsDirectory": "../../coverage/packages/theme-basic" + } } } } diff --git a/npm/ng-packs/packages/theme-basic/src/lib/tests/lazy-style.handler.spec.ts b/npm/ng-packs/packages/theme-basic/src/lib/tests/lazy-style.handler.spec.ts index dd0591f9fd..847c3418a6 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/tests/lazy-style.handler.spec.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/tests/lazy-style.handler.spec.ts @@ -1,23 +1,59 @@ -import { LazyLoadService, LOADING_STRATEGY, LocalizationService } from '@abp/ng.core'; +import { DOCUMENT } from '@angular/common'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; +import { of, Subject } from 'rxjs'; +import { vi } from 'vitest'; + +import { LazyLoadService, LoadingStrategy, LocalizationService } from '@abp/ng.core'; import { DocumentDirHandlerService } from '@abp/ng.theme.shared'; -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; -import { of } from 'rxjs'; import { BOOTSTRAP, createLazyStyleHref, LazyStyleHandler } from '../handlers'; - -const currentLang$ = of({ payload: 'en' }); +import { LAZY_STYLES } from '../tokens/lazy-styles.token'; +import { setupComponentResources } from './utils'; describe('LazyStyleHandler', () => { let spectator: SpectatorService; let handler: LazyStyleHandler; - let lazyLoad: LazyLoadService; + let lazyLoad: any; + + beforeAll(async () => { + await setupComponentResources( + '../components/breadcrumb', + import.meta.url + ); + }); + + const dir$ = new Subject<'ltr' | 'rtl'>(); const createService = createServiceFactory({ service: LazyStyleHandler, providers: [ - DocumentDirHandlerService, + { + provide: DOCUMENT, + useValue: document, + }, + { + provide: LAZY_STYLES, + useValue: [BOOTSTRAP], + }, + { + provide: LazyLoadService, + useValue: { + loaded: new Map(), + load: vi.fn(() => of(null)), + remove: vi.fn(), + }, + }, + { + provide: DocumentDirHandlerService, + useValue: { + dir$, + }, + }, { provide: LocalizationService, - useValue: { currentLang: 'en', currentLang$ }, + useValue: { + currentLang: 'en', + currentLang$: of({ payload: 'en' }), + }, }, ], }); @@ -25,7 +61,7 @@ describe('LazyStyleHandler', () => { beforeEach(() => { spectator = createService(); handler = spectator.service; - lazyLoad = handler['lazyLoad']; + lazyLoad = spectator.inject(LazyLoadService); }); describe('#dir', () => { @@ -36,15 +72,19 @@ describe('LazyStyleHandler', () => { it('should set bootstrap to rtl', () => { const oldHref = createLazyStyleHref(BOOTSTRAP, 'ltr'); const newHref = createLazyStyleHref(BOOTSTRAP, 'rtl'); - lazyLoad.loaded.set(newHref, null); // avoid actual loading - const load = jest.spyOn(lazyLoad, 'load'); - const remove = jest.spyOn(lazyLoad, 'remove'); - const strategy = LOADING_STRATEGY.PrependAnonymousStyleToHead(newHref); + + lazyLoad.loaded.set(newHref, null); + + const loadSpy = vi.spyOn(lazyLoad, 'load'); + const removeSpy = vi.spyOn(lazyLoad, 'remove'); handler.dir = 'rtl'; - expect(load).toHaveBeenCalledWith(strategy); - expect(remove).toHaveBeenCalledWith(oldHref); + expect(loadSpy).toHaveBeenCalledTimes(1); + const [strategy] = loadSpy.mock.calls[0]; + expect((strategy as LoadingStrategy).path).toBe(newHref); + + expect(removeSpy).toHaveBeenCalledWith(oldHref); }); }); }); diff --git a/npm/ng-packs/packages/theme-basic/src/lib/tests/utils/index.ts b/npm/ng-packs/packages/theme-basic/src/lib/tests/utils/index.ts new file mode 100644 index 0000000000..73b11724ec --- /dev/null +++ b/npm/ng-packs/packages/theme-basic/src/lib/tests/utils/index.ts @@ -0,0 +1 @@ +export * from './setup-component-resources'; \ No newline at end of file diff --git a/npm/ng-packs/packages/theme-basic/src/lib/tests/utils/setup-component-resources.ts b/npm/ng-packs/packages/theme-basic/src/lib/tests/utils/setup-component-resources.ts new file mode 100644 index 0000000000..0795a5e32b --- /dev/null +++ b/npm/ng-packs/packages/theme-basic/src/lib/tests/utils/setup-component-resources.ts @@ -0,0 +1,54 @@ +import { readFileSync } from 'node:fs'; +import { resolve, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +/** + * Sets up component resource resolution for Angular component tests. + * This is needed when components have external templates or stylesheets. + * + * @param componentDirPath - The path to the component directory relative to the test file. + * For example: '../components/loader-bar' or './components/my-component' + * @param testFileUrl - The import.meta.url from the test file. Defaults to the caller's location. + * + * @example + * ```typescript + * + * import { setupComponentResources } from './utils'; + * + * beforeAll(() => setupComponentResources('../components/loader-bar', import.meta.url)); + * ``` + */ +export async function setupComponentResources( + componentDirPath: string, + testFileUrl: string = import.meta.url, +): Promise { + try { + if (typeof process !== 'undefined' && process.versions?.node) { + const { ɵresolveComponentResources: resolveComponentResources } = await import('@angular/core'); + + // Get the test file directory path + const testFileDir = dirname(fileURLToPath(testFileUrl)); + const componentDir = resolve(testFileDir, componentDirPath); + + await resolveComponentResources((url: string) => { + // For SCSS/SASS files, return empty CSS since jsdom can't parse SCSS + if (url.endsWith('.scss') || url.endsWith('.sass')) { + return Promise.resolve(''); + } + + // For other files (HTML, CSS, etc.), read the actual content + try { + // Resolve relative paths like './component.scss' or 'component.scss' + const normalizedUrl = url.replace(/^\.\//, ''); + const filePath = resolve(componentDir, normalizedUrl); + return Promise.resolve(readFileSync(filePath, 'utf-8')); + } catch (error) { + // If file not found, return empty string + return Promise.resolve(''); + } + }); + } + } catch (error) { + console.warn('Failed to set up component resource resolver:', error); + } +} diff --git a/npm/ng-packs/packages/theme-basic/src/test-setup.ts b/npm/ng-packs/packages/theme-basic/src/test-setup.ts index 4555f138a7..b349bcf687 100644 --- a/npm/ng-packs/packages/theme-basic/src/test-setup.ts +++ b/npm/ng-packs/packages/theme-basic/src/test-setup.ts @@ -1,2 +1,20 @@ -import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone'; -setupZoneTestEnv(); +import '@angular/compiler'; +import 'zone.js'; +import 'zone.js/testing'; +import { getTestBed } from '@angular/core/testing'; +import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing'; + +// Initialize Angular testing environment +getTestBed().initTestEnvironment(BrowserTestingModule, platformBrowserTesting()); + +// Mock window.location for test environment +Object.defineProperty(window, 'location', { + value: { + href: 'http://localhost:4200', + origin: 'http://localhost:4200', + pathname: '/', + search: '', + hash: '', + }, + writable: true, +}); diff --git a/npm/ng-packs/packages/theme-basic/tsconfig.lib.json b/npm/ng-packs/packages/theme-basic/tsconfig.lib.json index 22d2695db8..80ebc37002 100644 --- a/npm/ng-packs/packages/theme-basic/tsconfig.lib.json +++ b/npm/ng-packs/packages/theme-basic/tsconfig.lib.json @@ -10,6 +10,23 @@ "lib": ["dom", "es2020"], "useDefineForClassFields": false }, - "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], + "exclude": [ + "src/test-setup.ts", + "**/*.spec.ts", + "jest.config.ts", + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/test-setup.ts" + ], "include": ["**/*.ts"] } diff --git a/npm/ng-packs/packages/theme-basic/tsconfig.spec.json b/npm/ng-packs/packages/theme-basic/tsconfig.spec.json index 023d7d0b51..fc61345bb3 100644 --- a/npm/ng-packs/packages/theme-basic/tsconfig.spec.json +++ b/npm/ng-packs/packages/theme-basic/tsconfig.spec.json @@ -2,10 +2,22 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", - "types": ["jest", "node"], - "esModuleInterop": true + "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node", "vitest"] }, - "files": ["src/test-setup.ts"], - "include": ["**/*.spec.ts", "**/*.d.ts", "jest.config.ts"] + "include": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "files": ["src/test-setup.ts"] } diff --git a/npm/ng-packs/packages/theme-basic/vitest.config.mts b/npm/ng-packs/packages/theme-basic/vitest.config.mts new file mode 100644 index 0000000000..f76013d8f4 --- /dev/null +++ b/npm/ng-packs/packages/theme-basic/vitest.config.mts @@ -0,0 +1,22 @@ +import { defineConfig } from 'vitest/config'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/theme-basic', + plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + test: { + name: 'theme-basic', + watch: false, + globals: true, + environment: 'jsdom', + setupFiles: ['src/test-setup.ts'], + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/packages/theme-basic', + provider: 'v8' as const, + }, + }, +})); diff --git a/npm/ng-packs/packages/theme-shared/.eslintrc.json b/npm/ng-packs/packages/theme-shared/.eslintrc.json index 5e303d20bb..afa6cfec5f 100644 --- a/npm/ng-packs/packages/theme-shared/.eslintrc.json +++ b/npm/ng-packs/packages/theme-shared/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "ignorePatterns": ["!**/*", "**/vitest.config.*.timestamp*"], "overrides": [ { "files": ["*.ts"], diff --git a/npm/ng-packs/packages/theme-shared/jest.config.ts b/npm/ng-packs/packages/theme-shared/jest.config.ts index af6440fc3e..c03a94ad03 100644 --- a/npm/ng-packs/packages/theme-shared/jest.config.ts +++ b/npm/ng-packs/packages/theme-shared/jest.config.ts @@ -1,4 +1,8 @@ /* eslint-disable */ +/** + * @deprecated use vitest instead of jest + * @see https://vitest.dev/guide/migration.html#jest + */ export default { displayName: 'theme-shared', preset: '../../jest.preset.js', diff --git a/npm/ng-packs/packages/theme-shared/package.json b/npm/ng-packs/packages/theme-shared/package.json index c2f64d4014..dc42e452a3 100644 --- a/npm/ng-packs/packages/theme-shared/package.json +++ b/npm/ng-packs/packages/theme-shared/package.json @@ -1,13 +1,13 @@ { "name": "@abp/ng.theme.shared", - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/abpframework/abp.git" }, "dependencies": { - "@abp/ng.core": "~10.1.0-rc.1", + "@abp/ng.core": "~10.1.0-rc.2", "@fortawesome/fontawesome-free": "^6.0.0", "@ng-bootstrap/ng-bootstrap": "~20.0.0", "@ngx-validate/core": "^0.2.0", diff --git a/npm/ng-packs/packages/theme-shared/project.json b/npm/ng-packs/packages/theme-shared/project.json index a77551187c..2bfa7e6574 100644 --- a/npm/ng-packs/packages/theme-shared/project.json +++ b/npm/ng-packs/packages/theme-shared/project.json @@ -23,16 +23,16 @@ }, "defaultConfiguration": "production" }, - "test": { - "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/packages/theme-shared"], - "options": { - "jestConfig": "packages/theme-shared/jest.config.ts" - } - }, "lint": { "executor": "@nx/eslint:lint", "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/vitest:test", + "outputs": ["{options.reportsDirectory}"], + "options": { + "reportsDirectory": "../../coverage/packages/theme-shared" + } } } } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb-items/breadcrumb-items.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb-items/breadcrumb-items.component.ts index 6e60a77669..a5a1a4d112 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb-items/breadcrumb-items.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb-items/breadcrumb-items.component.ts @@ -6,7 +6,7 @@ import { ABP, LocalizationPipe } from '@abp/ng.core'; @Component({ selector: 'abp-breadcrumb-items', templateUrl: './breadcrumb-items.component.html', - imports: [ NgTemplateOutlet, RouterLink, LocalizationPipe], + imports: [NgTemplateOutlet, RouterLink, LocalizationPipe], }) export class BreadcrumbItemsComponent { @Input() items: Partial[] = []; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/breadcrumb.component.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/breadcrumb.component.spec.ts index 5f84edac4f..d0d204dceb 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/breadcrumb.component.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/breadcrumb.component.spec.ts @@ -1,31 +1,25 @@ import { ABP, - CORE_OPTIONS, LocalizationPipe, RouterOutletComponent, RoutesService, - LocalizationService, + provideAbpCore, + withOptions, + RestService, + AbpApplicationConfigurationService, + ConfigStateService, } from '@abp/ng.core'; -import { provideHttpClient } from '@angular/common/http'; -import { provideHttpClientTesting } from '@angular/common/http/testing'; import { RouterModule } from '@angular/router'; -import { createRoutingFactory, SpectatorRouting } from '@ngneat/spectator/jest'; +import { createRoutingFactory, SpectatorRouting } from '@ngneat/spectator/vitest'; +import { of } from 'rxjs'; import { BreadcrumbComponent, BreadcrumbItemsComponent } from '../components'; -import { OTHERS_GROUP } from '@abp/ng.core'; -import { SORT_COMPARE_FUNC } from '@abp/ng.core'; +import { setupComponentResources } from './utils'; const mockRoutes: ABP.Route[] = [ { name: '_::Identity', path: '/identity' }, { name: '_::Users', path: '/identity/users', parentName: '_::Identity' }, ]; -// Simple compare function that doesn't use inject() -const simpleCompareFunc = (a: any, b: any) => { - const aNumber = a.order || 0; - const bNumber = b.order || 0; - return aNumber - bNumber; -}; - describe('BreadcrumbComponent', () => { let spectator: SpectatorRouting; let routes: RoutesService; @@ -34,39 +28,58 @@ describe('BreadcrumbComponent', () => { component: RouterOutletComponent, stubsEnabled: false, detectChanges: false, + imports: [ + RouterModule, + LocalizationPipe, + BreadcrumbComponent, + BreadcrumbItemsComponent, + ], providers: [ - provideHttpClient(), - provideHttpClientTesting(), - { - provide: CORE_OPTIONS, - useValue: { + provideAbpCore( + withOptions({ environment: { apis: { default: { url: 'http://localhost:4200', }, }, + application: { + name: 'TestApp', + baseUrl: 'http://localhost:4200', + }, }, - } + registerLocaleFn: () => Promise.resolve(), + skipGetAppConfiguration: true, + }), + ), + { + provide: RestService, + useValue: { + request: vi.fn(), + handleError: vi.fn(), + }, }, - RoutesService, - LocalizationService, { - provide: OTHERS_GROUP, - useValue: 'AbpUi::OthersGroup', + provide: AbpApplicationConfigurationService, + useValue: { + get: vi.fn(), + }, }, { - provide: SORT_COMPARE_FUNC, - useValue: simpleCompareFunc, + provide: ConfigStateService, + useValue: { + getOne: vi.fn(), + getAll: vi.fn(() => ({})), + getAll$: vi.fn(() => of({})), + getDeep: vi.fn(), + getDeep$: vi.fn(() => of(undefined)), + createOnUpdateStream: vi.fn(() => ({ + subscribe: vi.fn(() => ({ unsubscribe: vi.fn() })) + })), + refreshAppState: vi.fn(), + }, }, ], - declarations: [], - imports: [ - RouterModule, - LocalizationPipe, - BreadcrumbComponent, - BreadcrumbItemsComponent, - ], routes: [ { path: '', @@ -85,6 +98,8 @@ describe('BreadcrumbComponent', () => { ], }); + beforeAll(() => setupComponentResources('../components/breadcrumb', import.meta.url)); + beforeEach(() => { spectator = createRouting(); routes = spectator.inject(RoutesService); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/button.component.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/button.component.spec.ts index bf834d182d..2986e7c14e 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/button.component.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/button.component.spec.ts @@ -1,4 +1,4 @@ -import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest'; +import { createHostFactory, SpectatorHost } from '@ngneat/spectator/vitest'; import { ButtonComponent } from '../components'; describe('ButtonComponent', () => { diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/card.component.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/card.component.spec.ts index fa474640f6..9990b33676 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/card.component.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/card.component.spec.ts @@ -1,4 +1,4 @@ -import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest'; +import { createHostFactory, SpectatorHost } from '@ngneat/spectator/vitest'; import { CardComponent, CardBodyComponent, diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/checkbox.component.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/checkbox.component.spec.ts index e2fba6d0da..4ea0946c01 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/checkbox.component.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/checkbox.component.spec.ts @@ -1,4 +1,4 @@ -import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest'; +import { createHostFactory, SpectatorHost } from '@ngneat/spectator/vitest'; import { FormCheckboxComponent } from '../components/checkbox/checkbox.component'; describe('FormCheckboxComponent', () => { diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/confirmation.service.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/confirmation.service.spec.ts index 029becaa96..d03e69733d 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/confirmation.service.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/confirmation.service.spec.ts @@ -1,27 +1,24 @@ import { CoreTestingModule } from '@abp/ng.core/testing'; -import { NgModule } from '@angular/core'; -import { fakeAsync, tick } from '@angular/core/testing'; -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; -import { timer } from 'rxjs'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; +import { firstValueFrom, timer } from 'rxjs'; import { ConfirmationComponent } from '../components'; import { Confirmation } from '../models'; import { ConfirmationService } from '../services'; import { CONFIRMATION_ICONS, DEFAULT_CONFIRMATION_ICONS } from '../tokens/confirmation-icons.token'; - -@NgModule({ - exports: [ConfirmationComponent], - declarations: [], - imports: [CoreTestingModule.withConfig(), ConfirmationComponent], - providers: [{ provide: CONFIRMATION_ICONS, useValue: DEFAULT_CONFIRMATION_ICONS }], -}) -export class MockModule {} +import { setupComponentResources } from './utils'; describe('ConfirmationService', () => { let spectator: SpectatorService; let service: ConfirmationService; + const createService = createServiceFactory({ service: ConfirmationService, - imports: [CoreTestingModule.withConfig(), MockModule], + imports: [CoreTestingModule.withConfig(), ConfirmationComponent], + providers: [{ provide: CONFIRMATION_ICONS, useValue: DEFAULT_CONFIRMATION_ICONS }], + }); + + beforeAll(async () => { + await setupComponentResources('../components/confirmation', import.meta.url); }); beforeEach(() => { @@ -33,16 +30,16 @@ describe('ConfirmationService', () => { clearElements(); }); - test('should display a confirmation popup', fakeAsync(() => { + test('should display a confirmation popup', async () => { service.show('_::MESSAGE', '_::TITLE'); - tick(); + await firstValueFrom(timer(10)); expect(selectConfirmationContent('.title')).toBe('TITLE'); expect(selectConfirmationContent('.message')).toBe('MESSAGE'); - })); + }); - test('should display HTML string in title, message, and buttons', fakeAsync(() => { + test('should display HTML string in title, message, and buttons', async () => { service.show( '_::MESSAGE', '_::TITLE', @@ -53,24 +50,24 @@ describe('ConfirmationService', () => { }, ); - tick(); + await firstValueFrom(timer(10)); expect(selectConfirmationContent('.custom-title')).toBe('TITLE'); expect(selectConfirmationContent('.custom-message')).toBe('MESSAGE'); expect(selectConfirmationContent('.custom-cancel')).toBe('CANCEL'); expect(selectConfirmationContent('.custom-yes')).toBe('YES'); - })); + }); - test('should display custom FA icon', fakeAsync(() => { + test('should display custom FA icon', async () => { service.show('_::MESSAGE', '_::TITLE', undefined, { icon: 'fa fa-info', }); - tick(); + await firstValueFrom(timer(10)); expect(selectConfirmationElement('.icon').className).toBe('icon fa fa-info'); - })); + }); - test('should display custom icon as html element', fakeAsync(() => { + test('should display custom icon as html element', async () => { const className = 'custom-icon'; const selector = '.' + className; @@ -78,12 +75,14 @@ describe('ConfirmationService', () => { iconTemplate: `I am icon`, }); - tick(); + await firstValueFrom(timer(10)); const element = selectConfirmationElement(selector); expect(element).toBeTruthy(); expect(element.innerHTML).toBe('I am icon'); - })); + }); + + test.each` type | selector | icon ${'info'} | ${'.info'} | ${'.fa-info-circle'} @@ -93,7 +92,7 @@ describe('ConfirmationService', () => { `('should display $type confirmation popup', async ({ type, selector, icon }) => { service[type]('_::MESSAGE', '_::TITLE'); - await timer(0).toPromise(); + await firstValueFrom(timer(10)); expect(selectConfirmationContent('.title')).toBe('TITLE'); expect(selectConfirmationContent('.message')).toBe('MESSAGE'); @@ -101,31 +100,18 @@ describe('ConfirmationService', () => { expect(selectConfirmationElement(icon)).toBeTruthy(); }); - // test('should close with ESC key', (done) => { - // service - // .info('', '') - // .pipe(take(1)) - // .subscribe((status) => { - // expect(status).toBe(Confirmation.Status.dismiss); - // done(); - // }); - // const escape = new KeyboardEvent('keyup', { key: 'Escape' }); - // document.dispatchEvent(escape); - // }); - - test('should close when click cancel button', done => { + test('should close when click cancel button', async () => { service.info('_::', '_::', { yesText: '_::Sure', cancelText: '_::Exit' }).subscribe(status => { expect(status).toBe(Confirmation.Status.reject); - done(); }); - timer(0).subscribe(() => { - expect(selectConfirmationContent('button#cancel')).toBe('Exit'); - expect(selectConfirmationContent('button#confirm')).toBe('Sure'); + await firstValueFrom(timer(10)); - (document.querySelector('button#cancel') as HTMLButtonElement).click(); - }); + expect(selectConfirmationContent('button#cancel')).toBe('Exit'); + expect(selectConfirmationContent('button#confirm')).toBe('Sure'); + + (document.querySelector('button#cancel') as HTMLButtonElement).click(); }); test.each` @@ -135,9 +121,9 @@ describe('ConfirmationService', () => { `( 'should call the listenToEscape method $count times when dismissible is $dismissible', ({ dismissible, count }) => { - const spy = jest.spyOn(service as any, 'listenToEscape'); + const spy = vi.spyOn(service as any, 'listenToEscape'); - service.info('_::', '_::', { dismissible }); + service.info('_::', '_::', { dismissible }); expect(spy).toHaveBeenCalledTimes(count); }, diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/ellipsis.directive.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/ellipsis.directive.spec.ts index 727a1c5312..e9b0e45a0d 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/ellipsis.directive.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/ellipsis.directive.spec.ts @@ -1,4 +1,4 @@ -import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/jest'; +import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/vitest'; import { EllipsisDirective } from '../directives/ellipsis.directive'; describe('EllipsisDirective', () => { @@ -39,17 +39,61 @@ describe('EllipsisDirective', () => { expect(directive.title).toBe('test title'); }); - test('should have element innerText as title if not specified', () => { - spectator.setHostInput({ title: undefined }); + test('should add abp-ellipsis-inline class to element if width is given', () => { + expect(el).toHaveClass('abp-ellipsis-inline'); + }); +}); + +describe('EllipsisDirective when title is not specified', () => { + let spectator: SpectatorDirective; + let directive: EllipsisDirective; + let el: HTMLDivElement; + const createDirective = createDirectiveFactory({ + directive: EllipsisDirective, + }); + + beforeEach(() => { + spectator = createDirective( + '
test content
', + { + hostProps: { + title: undefined, + width: '100px', + }, + }, + ); + directive = spectator.directive; + el = spectator.query('div') as HTMLDivElement; + }); + + test('should have element innerText as title', () => { expect(directive.title).toBe(el.innerText); }); +}); - test('should add abp-ellipsis-inline class to element if width is given', () => { - expect(el).toHaveClass('abp-ellipsis-inline'); +describe('EllipsisDirective when width is not given', () => { + let spectator: SpectatorDirective; + let directive: EllipsisDirective; + let el: HTMLDivElement; + const createDirective = createDirectiveFactory({ + directive: EllipsisDirective, + }); + + beforeEach(() => { + spectator = createDirective( + '
test content
', + { + hostProps: { + title: 'test title', + width: undefined, + }, + }, + ); + directive = spectator.directive; + el = spectator.query('div') as HTMLDivElement; }); - test('should add abp-ellipsis class to element if width is not given', () => { - spectator.setHostInput({ width: undefined }); + test('should add abp-ellipsis class to element', () => { expect(el).toHaveClass('abp-ellipsis'); }); }); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/error.component.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/error.component.spec.ts index 93b8d32f0f..d1cb9a5940 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/error.component.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/error.component.spec.ts @@ -1,49 +1,84 @@ -import { CORE_OPTIONS, LocalizationPipe } from '@abp/ng.core'; -import { HttpClient, HttpClientModule } from '@angular/common/http'; -import { ElementRef, Renderer2 } from '@angular/core'; -import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest'; +import { DOCUMENT } from '@angular/common'; +import { Router } from '@angular/router'; +import { createComponentFactory, Spectator } from '@ngneat/spectator/vitest'; +import { Pipe, PipeTransform } from '@angular/core'; import { Subject } from 'rxjs'; +import { vi } from 'vitest'; + import { HttpErrorWrapperComponent } from '../components/http-error-wrapper/http-error-wrapper.component'; +import { setupComponentResources } from './utils'; + +/** + * Mock pipe to avoid ABP DI chain + */ +@Pipe({ name: 'abpLocalization'}) +class MockLocalizationPipe implements PipeTransform { + transform(value: any): any { + return value; + } +} + +describe('HttpErrorWrapperComponent', () => { + let spectator: Spectator; + let createComponent: ReturnType>; -describe('ErrorComponent', () => { - let spectator: SpectatorHost; - const createHost = createHostFactory({ - component: HttpErrorWrapperComponent, - declarations: [], - mocks: [HttpClient], - providers: [ - { provide: CORE_OPTIONS, useValue: {} }, - { provide: Renderer2, useValue: { removeChild: () => null } }, - { - provide: ElementRef, - useValue: { nativeElement: document.createElement('div') }, - }, - ], - imports: [HttpClientModule, LocalizationPipe], + beforeAll(async () => { + await setupComponentResources( + '../components/http-error-wrapper', + import.meta.url, + ); }); - beforeEach(() => { - spectator = createHost( - '', - ); - spectator.component.destroy$ = new Subject(); - }); - - describe('#destroy', () => { - it('should be call when pressed the esc key', done => { - spectator.component.destroy$.subscribe(() => { - done(); - }); + beforeEach(() => { + if (!createComponent) { + createComponent = createComponentFactory({ + component: HttpErrorWrapperComponent, + detectChanges: false, - spectator.keyboard.pressEscape(); - }); + overrideComponents: [ + [ + HttpErrorWrapperComponent, + { + set: { + template: '
', + imports: [MockLocalizationPipe], + }, + }, + ], + ], - it('should be call when clicked the close button', done => { - spectator.component.destroy$.subscribe(() => { - done(); + providers: [ + { + provide: DOCUMENT, + useValue: document, + }, + { + provide: Router, + useValue: { + navigateByUrl: vi.fn(), + }, + }, + ], }); + } + + spectator = createComponent(); + + spectator.component.destroy$ = new Subject(); + spectator.component.title = '_::Oops!'; + spectator.component.details = '_::Sorry, an error has occured.'; + }); + + it('should create component', () => { + expect(spectator.component).toBeTruthy(); + }); + + it('should emit destroy$ when destroy is called', () => { + const spy = vi.fn(); + spectator.component.destroy$.subscribe(spy); + + spectator.component.destroy(); - spectator.click('#abp-close-button'); - }); + expect(spy).toHaveBeenCalled(); }); }); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/error.handler.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/error.handler.spec.ts index 75726103d2..d57f75af28 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/error.handler.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/error.handler.spec.ts @@ -2,46 +2,48 @@ import { HttpErrorReporterService } from '@abp/ng.core'; import { CoreTestingModule } from '@abp/ng.core/testing'; import { APP_BASE_HREF } from '@angular/common'; import { HttpErrorResponse, HttpHeaders } from '@angular/common/http'; -import { NgModule } from '@angular/core'; -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; import { OAuthService } from 'angular-oauth2-oidc'; import { of, Subject } from 'rxjs'; -import { HttpErrorWrapperComponent } from '../components/http-error-wrapper/http-error-wrapper.component'; import { ErrorHandler } from '../handlers'; import { ConfirmationService } from '../services'; +import { CreateErrorComponentService } from '../services/create-error-component.service'; +import { RouterErrorHandlerService } from '../services/router-error-handler.service'; import { CUSTOM_ERROR_HANDLERS, HTTP_ERROR_CONFIG } from '../tokens/http-error.token'; import { CustomHttpErrorHandlerService } from '../models'; const customHandlerMock: CustomHttpErrorHandlerService = { priority: 100, - canHandle: jest.fn().mockReturnValue(true), - execute: jest.fn(), + canHandle: vi.fn().mockReturnValue(true), + execute: vi.fn(), }; const reporter$ = new Subject(); -@NgModule({ - exports: [HttpErrorWrapperComponent], - declarations: [], - imports: [CoreTestingModule, HttpErrorWrapperComponent], -}) -class MockModule {} - let spectator: SpectatorService; let service: ErrorHandler; let httpErrorReporter: HttpErrorReporterService; -const errorConfirmation: jest.Mock = jest.fn(() => of(null)); -const CONFIRMATION_BUTTONS = { - hideCancelBtn: true, - yesText: 'AbpAccount::Close', -}; +const errorConfirmation = vi.fn(() => of(null)); + describe('ErrorHandler', () => { const createService = createServiceFactory({ service: ErrorHandler, - imports: [CoreTestingModule.withConfig(), MockModule], + imports: [CoreTestingModule.withConfig()], mocks: [OAuthService], providers: [ + { + provide: RouterErrorHandlerService, + useValue: { + listen: vi.fn(), + }, + }, + { + provide: CreateErrorComponentService, + useValue: { + execute: vi.fn(), + }, + }, { provide: HttpErrorReporterService, useValue: { @@ -65,7 +67,10 @@ describe('ErrorHandler', () => { }, { provide: HTTP_ERROR_CONFIG, - useFactory: () => ({}), + useValue: { + skipHandledErrorCodes: [], + errorScreen: {}, + }, }, ], }); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/form-input.component.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/form-input.component.spec.ts index cf4eb4aac3..e6ae275a95 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/form-input.component.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/form-input.component.spec.ts @@ -1,4 +1,4 @@ -import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest'; +import { createHostFactory, SpectatorHost } from '@ngneat/spectator/vitest'; import { FormInputComponent } from '../components/form-input/form-input.component'; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/loader-bar.component.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/loader-bar.component.spec.ts index e2904e33f1..a825cbf7db 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/loader-bar.component.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/loader-bar.component.spec.ts @@ -1,26 +1,32 @@ -import { HttpWaitService, LOADER_DELAY, SubscriptionService } from '@abp/ng.core'; +import { HttpWaitService, LOADER_DELAY, RouterWaitService, SubscriptionService } from '@abp/ng.core'; import { HttpRequest } from '@angular/common/http'; -import { NavigationEnd, NavigationError, NavigationStart, Router } from '@angular/router'; -import { createComponentFactory, Spectator, SpyObject } from '@ngneat/spectator/jest'; -import { Subject, timer } from 'rxjs'; +import { NavigationStart, Router } from '@angular/router'; +import { createComponentFactory, Spectator } from '@ngneat/spectator/vitest'; +import { combineLatest, firstValueFrom, Subject, timer } from 'rxjs'; import { LoaderBarComponent } from '../components/loader-bar/loader-bar.component'; +import { setupComponentResources } from './utils'; describe('LoaderBarComponent', () => { let spectator: Spectator; let router: Router; + let createComponent: ReturnType>; const events$ = new Subject(); - const createComponent = createComponentFactory({ - component: LoaderBarComponent, - detectChanges: false, - providers: [ - SubscriptionService, - { provide: Router, useValue: { events: events$ } }, - { provide: LOADER_DELAY, useValue: 0 }, - ], - }); + beforeAll(() => setupComponentResources('../components/loader-bar', import.meta.url)); beforeEach(() => { + if (!createComponent) { + createComponent = createComponentFactory({ + component: LoaderBarComponent, + detectChanges: false, + providers: [ + SubscriptionService, + { provide: Router, useValue: { events: events$ } }, + { provide: LOADER_DELAY, useValue: 0 }, + ], + }); + } + spectator = createComponent({}); spectator.component.intervalPeriod = 1; spectator.component.stopDelay = 1; @@ -32,66 +38,127 @@ describe('LoaderBarComponent', () => { expect(spectator.component.color).toBe('#77b6ff'); }); - it('should increase the progressLevel', done => { + it('should increase the progressLevel', async () => { spectator.detectChanges(); const httpWaitService = spectator.inject(HttpWaitService); httpWaitService.addRequest(new HttpRequest('GET', 'test')); spectator.detectChanges(); - setTimeout(() => { - expect(spectator.component.progressLevel > 0).toBeTruthy(); - done(); - }, 10); + + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(spectator.component.progressLevel > 0).toBeTruthy(); }); - it('should be interval unsubscribed', done => { - const request = new HttpRequest('GET', 'test'); + it('should be interval unsubscribed', async () => { + const request = new HttpRequest('GET', 'test'); spectator.detectChanges(); const httpWaitService = spectator.inject(HttpWaitService); + + await firstValueFrom(combineLatest([ + httpWaitService.getLoading$(), + spectator.inject(RouterWaitService).getLoading$() + ])); + httpWaitService.addRequest(request); + spectator.detectChanges(); + + let attempts = 0; + while (spectator.component.interval.closed && attempts < 50) { + await new Promise(resolve => setTimeout(resolve, 10)); + spectator.detectChanges(); + attempts++; + } + expect(spectator.component.interval.closed).toBe(false); + httpWaitService.deleteRequest(request); - timer(400).subscribe(() => { - expect(spectator.component.interval.closed).toBe(true); - done(); - }); + spectator.detectChanges(); + + await firstValueFrom(timer(400)); + + expect(spectator.component.interval.closed).toBe(true); }); - it('should start and stop the loading with navigation', done => { + + it('should start and stop the loading with navigation', async () => { + spectator.detectChanges(); + const routerWaitService = spectator.inject(RouterWaitService); + + routerWaitService.setLoading(true); spectator.detectChanges(); - events$.next(new NavigationStart(1, 'test')); + + let attempts = 0; + while (spectator.component.interval.closed && attempts < 50) { + await new Promise(resolve => setTimeout(resolve, 10)); + spectator.detectChanges(); + attempts++; + } expect(spectator.component.interval.closed).toBe(false); - events$.next(new NavigationEnd(1, 'test', 'test')); - events$.next(new NavigationError(1, 'test', 'test')); + routerWaitService.setLoading(false); + spectator.detectChanges(); + + attempts = 0; + while (spectator.component.progressLevel !== 100 && attempts < 50) { + await new Promise(resolve => setTimeout(resolve, 10)); + spectator.detectChanges(); + attempts++; + } expect(spectator.component.progressLevel).toBe(100); - timer(2).subscribe(() => { - expect(spectator.component.progressLevel).toBe(0); - done(); - }); + await firstValueFrom(timer(spectator.component.stopDelay + 10)); + expect(spectator.component.progressLevel).toBe(0); }); - it('should stop the loading with navigation', done => { + it('should stop the loading with navigation', async () => { + spectator.detectChanges(); + const routerWaitService = spectator.inject(RouterWaitService); + + routerWaitService.setLoading(true); spectator.detectChanges(); - events$.next(new NavigationStart(1, 'test')); + + let attempts = 0; + while (spectator.component.interval.closed && attempts < 50) { + await new Promise(resolve => setTimeout(resolve, 10)); + spectator.detectChanges(); + attempts++; + } expect(spectator.component.interval.closed).toBe(false); - events$.next(new NavigationEnd(1, 'testend', 'testend')); + routerWaitService.setLoading(false); + spectator.detectChanges(); + + attempts = 0; + while (spectator.component.progressLevel !== 100 && attempts < 50) { + await new Promise(resolve => setTimeout(resolve, 10)); + spectator.detectChanges(); + attempts++; + } expect(spectator.component.progressLevel).toBe(100); - timer(2).subscribe(() => { - expect(spectator.component.progressLevel).toBe(0); - done(); - }); + await firstValueFrom(timer(spectator.component.stopDelay + 10)); + expect(spectator.component.progressLevel).toBe(0); }); describe('#startLoading', () => { - it('should return when isLoading is true', done => { + it('should return when isLoading is true', async () => { spectator.detectChanges(); + events$.next(new NavigationStart(1, 'test')); + spectator.detectChanges(); + + let attempts = 0; + while (spectator.component.interval.closed && attempts < 50) { + await new Promise(resolve => setTimeout(resolve, 10)); + spectator.detectChanges(); + attempts++; + } + events$.next(new NavigationStart(1, 'test')); - done(); + spectator.detectChanges(); + + expect(spectator.component).toBeTruthy(); }); }); }); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/loading.directive.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/loading.directive.spec.ts index 91589f2155..f80388d478 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/loading.directive.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/loading.directive.spec.ts @@ -1,4 +1,4 @@ -import { SpectatorDirective, createDirectiveFactory } from '@ngneat/spectator/jest'; +import { SpectatorDirective, createDirectiveFactory } from '@ngneat/spectator/vitest'; import { LoadingDirective } from '../directives'; import { LoadingComponent } from '../components'; import { Component } from '@angular/core'; @@ -29,10 +29,11 @@ describe('LoadingDirective', () => { expect(spectator.directive).toBeTruthy(); }); - it('should handle loading input', () => { - spectator.setHostInput({ loading: false }); - spectator.detectChanges(); + it('should handle loading input', async () => { + spectator.directive.loading = false; + await new Promise(resolve => setTimeout(resolve, 10)); expect(spectator.directive).toBeTruthy(); + expect(spectator.directive.loading).toBe(false); }); }); @@ -53,19 +54,19 @@ describe('LoadingDirective', () => { expect(spectator.directive.targetElement).toBe(mockTarget); }); - it('should handle delay input', () => { - spectator.setHostInput({ delay: 100 }); - spectator.detectChanges(); + it('should handle delay input', async () => { + spectator.directive.delay = 100; + await new Promise(resolve => setTimeout(resolve, 10)); expect(spectator.directive).toBeTruthy(); }); - it('should handle loading state changes', () => { - spectator.setHostInput({ loading: false }); - spectator.detectChanges(); + it('should handle loading state changes', async() => { + spectator.directive.loading = false; + await new Promise(resolve => setTimeout(resolve, 10)); expect(spectator.directive).toBeTruthy(); - spectator.setHostInput({ loading: true }); - spectator.detectChanges(); + spectator.directive.loading = true; + await new Promise(resolve => setTimeout(resolve, 10)); expect(spectator.directive).toBeTruthy(); }); }); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/modal.component.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/modal.component.spec.ts index dc79c47a9c..0caef230ca 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/modal.component.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/modal.component.spec.ts @@ -1,10 +1,11 @@ import { ConfirmationService } from '@abp/ng.theme.shared'; import { CoreTestingModule } from '@abp/ng.core/testing'; import { Component, EventEmitter, Input } from '@angular/core'; -import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; +import { createComponentFactory, Spectator } from '@ngneat/spectator/vitest'; import { Confirmation } from '@abp/ng.theme.shared'; -import { Subject, timer } from 'rxjs'; +import { firstValueFrom, Subject, timer } from 'rxjs'; import { ModalComponent } from '../components/modal/modal.component'; +import { setupComponentResources } from './utils'; @Component({ template: ` @@ -27,25 +28,34 @@ class TestHostComponent { } const mockConfirmation$ = new Subject(); -const disappearFn = jest.fn(); +const disappearFn = vi.fn(); describe('ModalComponent', () => { let spectator: Spectator; + let createComponent: ReturnType>; - const createComponent = createComponentFactory({ - component: TestHostComponent, - imports: [CoreTestingModule.withConfig()], - providers: [ - { - provide: ConfirmationService, - useValue: { - warn: jest.fn(() => mockConfirmation$), - }, - }, - ], - }); + beforeAll(() => setupComponentResources('../components/modal', import.meta.url)); beforeEach(() => { + // Create component factory in beforeEach to ensure beforeAll has run + if (!createComponent) { + createComponent = createComponentFactory({ + component: TestHostComponent, + imports: [ + CoreTestingModule.withConfig(), + ModalComponent, + ], + providers: [ + { + provide: ConfirmationService, + useValue: { + warn: vi.fn(() => mockConfirmation$), + }, + }, + ], + }); + } + spectator = createComponent(); disappearFn.mockClear(); }); @@ -71,10 +81,10 @@ describe('ModalComponent', () => { }); }); -async function wait0ms() { - await timer(0).toPromise(); +async function wait0ms() { + await firstValueFrom(timer(0)); } -async function wait300ms() { - await timer(300).toPromise(); +async function wait300ms() { + await firstValueFrom(timer(300)); } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/toaster.service.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/toaster.service.spec.ts index ae008d73ee..401da50070 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/toaster.service.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/toaster.service.spec.ts @@ -1,23 +1,25 @@ -import { CoreTestingModule } from '@abp/ng.core/testing'; -import { NgModule } from '@angular/core'; -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { ContentProjectionService } from '@abp/ng.core'; +import { ComponentRef } from '@angular/core'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest'; +import { beforeEach, describe, expect, test, vi } from 'vitest'; import { ToastContainerComponent } from '../components/toast-container/toast-container.component'; -import { ToastComponent } from '../components/toast/toast.component'; import { ToasterService } from '../services/toaster.service'; -@NgModule({ - exports: [ToastContainerComponent], - declarations: [], - imports: [CoreTestingModule.withConfig(), ToastContainerComponent, ToastComponent], -}) -export class MockModule {} - describe('ToasterService', () => { let spectator: SpectatorService; let service: ToasterService; + const mockComponentRef = { + changeDetectorRef: { detectChanges: vi.fn() }, + instance: {} as ToastContainerComponent, + } as unknown as ComponentRef; + + const contentProjectionService = { + projectContent: vi.fn().mockReturnValue(mockComponentRef), + } satisfies Partial; + const createService = createServiceFactory({ service: ToasterService, - imports: [CoreTestingModule.withConfig(), MockModule], + providers: [{ provide: ContentProjectionService, useValue: contentProjectionService }], }); beforeEach(() => { diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/utils/index.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/utils/index.ts new file mode 100644 index 0000000000..73b11724ec --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/utils/index.ts @@ -0,0 +1 @@ +export * from './setup-component-resources'; \ No newline at end of file diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/utils/setup-component-resources.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/utils/setup-component-resources.ts new file mode 100644 index 0000000000..0795a5e32b --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/utils/setup-component-resources.ts @@ -0,0 +1,54 @@ +import { readFileSync } from 'node:fs'; +import { resolve, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +/** + * Sets up component resource resolution for Angular component tests. + * This is needed when components have external templates or stylesheets. + * + * @param componentDirPath - The path to the component directory relative to the test file. + * For example: '../components/loader-bar' or './components/my-component' + * @param testFileUrl - The import.meta.url from the test file. Defaults to the caller's location. + * + * @example + * ```typescript + * + * import { setupComponentResources } from './utils'; + * + * beforeAll(() => setupComponentResources('../components/loader-bar', import.meta.url)); + * ``` + */ +export async function setupComponentResources( + componentDirPath: string, + testFileUrl: string = import.meta.url, +): Promise { + try { + if (typeof process !== 'undefined' && process.versions?.node) { + const { ɵresolveComponentResources: resolveComponentResources } = await import('@angular/core'); + + // Get the test file directory path + const testFileDir = dirname(fileURLToPath(testFileUrl)); + const componentDir = resolve(testFileDir, componentDirPath); + + await resolveComponentResources((url: string) => { + // For SCSS/SASS files, return empty CSS since jsdom can't parse SCSS + if (url.endsWith('.scss') || url.endsWith('.sass')) { + return Promise.resolve(''); + } + + // For other files (HTML, CSS, etc.), read the actual content + try { + // Resolve relative paths like './component.scss' or 'component.scss' + const normalizedUrl = url.replace(/^\.\//, ''); + const filePath = resolve(componentDir, normalizedUrl); + return Promise.resolve(readFileSync(filePath, 'utf-8')); + } catch (error) { + // If file not found, return empty string + return Promise.resolve(''); + } + }); + } + } catch (error) { + console.warn('Failed to set up component resource resolver:', error); + } +} diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/validation-utils.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/validation-utils.spec.ts index 9f263779e2..1fcd5c3591 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/validation-utils.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/validation-utils.spec.ts @@ -1,8 +1,9 @@ import { AbpApplicationConfigurationService, ConfigStateService } from '@abp/ng.core'; import { CoreTestingModule } from '@abp/ng.core/testing'; +import { AbpApplicationLocalizationService } from '@abp/ng.core'; import { HttpClient } from '@angular/common/http'; import { Component, Injector } from '@angular/core'; -import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; +import { createComponentFactory, Spectator } from '@ngneat/spectator/vitest'; import { OAuthService } from 'angular-oauth2-oidc'; import { of } from 'rxjs'; import { getPasswordValidators, validatePassword } from '../utils'; @@ -32,6 +33,44 @@ describe('ValidationUtils', () => { 'Abp.Identity.Password.RequireDigit': 'True', }, }, + localization: { + values: {}, + languages: [], + currentCulture: { + cultureName: 'en', + displayName: 'English', + englishName: 'English', + threeLetterIsoLanguageName: 'eng', + twoLetterIsoLanguageName: 'en', + isRightToLeft: false, + name: 'en', + nativeName: 'English', + dateTimeFormat: { + calendarAlgorithmType: 'SolarCalendar', + dateTimeFormatLong: 'dddd, MMMM d, yyyy', + shortDatePattern: 'M/d/yyyy', + fullDateTimePattern: 'dddd, MMMM d, yyyy h:mm:ss tt', + dateSeparator: '/', + shortTimePattern: 'h:mm tt', + longTimePattern: 'h:mm:ss tt', + }, + }, + defaultResourceName: null, + resources: {}, + languagesMap: {}, + languageFilesMap: {}, + }, + }), + }, + }, + { + provide: AbpApplicationLocalizationService, + useValue: { + get: () => + of({ + resources: { + Default: { texts: {}, baseResources: [] }, + }, }), }, }, diff --git a/npm/ng-packs/packages/theme-shared/src/test-setup.ts b/npm/ng-packs/packages/theme-shared/src/test-setup.ts index 2cf1ac7191..3eaa53b84d 100644 --- a/npm/ng-packs/packages/theme-shared/src/test-setup.ts +++ b/npm/ng-packs/packages/theme-shared/src/test-setup.ts @@ -1,10 +1,28 @@ -import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone'; -setupZoneTestEnv(); - -const originalError = console.error; -console.error = (...args: any[]) => { - if (args[0]?.includes?.('ExpressionChangedAfterItHasBeenCheckedError')) { - return; - } - originalError.apply(console, args); -}; +import '@angular/compiler'; +import 'zone.js'; +import 'zone.js/testing'; +import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing'; +import { + ɵgetCleanupHook as getCleanupHook, + getTestBed +} from '@angular/core/testing'; + + +beforeEach(getCleanupHook(false)); +afterEach(getCleanupHook(true)); + +// Initialize Angular testing environment +getTestBed().initTestEnvironment(BrowserTestingModule, platformBrowserTesting()); + + +// Mock window.location for test environment +Object.defineProperty(window, 'location', { + value: { + href: 'http://localhost:4200', + origin: 'http://localhost:4200', + pathname: '/', + search: '', + hash: '', + }, + writable: true, +}); diff --git a/npm/ng-packs/packages/theme-shared/tsconfig.lib.json b/npm/ng-packs/packages/theme-shared/tsconfig.lib.json index 22d2695db8..80ebc37002 100644 --- a/npm/ng-packs/packages/theme-shared/tsconfig.lib.json +++ b/npm/ng-packs/packages/theme-shared/tsconfig.lib.json @@ -10,6 +10,23 @@ "lib": ["dom", "es2020"], "useDefineForClassFields": false }, - "exclude": ["src/test-setup.ts", "**/*.spec.ts", "jest.config.ts"], + "exclude": [ + "src/test-setup.ts", + "**/*.spec.ts", + "jest.config.ts", + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/test-setup.ts" + ], "include": ["**/*.ts"] } diff --git a/npm/ng-packs/packages/theme-shared/tsconfig.spec.json b/npm/ng-packs/packages/theme-shared/tsconfig.spec.json index 023d7d0b51..fc61345bb3 100644 --- a/npm/ng-packs/packages/theme-shared/tsconfig.spec.json +++ b/npm/ng-packs/packages/theme-shared/tsconfig.spec.json @@ -2,10 +2,22 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", - "module": "commonjs", - "types": ["jest", "node"], - "esModuleInterop": true + "types": ["vitest/globals", "vitest/importMeta", "vite/client", "node", "vitest"] }, - "files": ["src/test-setup.ts"], - "include": ["**/*.spec.ts", "**/*.d.ts", "jest.config.ts"] + "include": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "files": ["src/test-setup.ts"] } diff --git a/npm/ng-packs/packages/theme-shared/vitest.config.mts b/npm/ng-packs/packages/theme-shared/vitest.config.mts new file mode 100644 index 0000000000..d9496b26fd --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/vitest.config.mts @@ -0,0 +1,22 @@ +import { defineConfig } from 'vitest/config'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/theme-shared', + plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + test: { + name: 'theme-shared', + watch: false, + globals: true, + environment: 'jsdom', + setupFiles: ['src/test-setup.ts'], + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/packages/theme-shared', + provider: 'v8' as const, + }, + }, +})); diff --git a/npm/ng-packs/tsconfig.base.json b/npm/ng-packs/tsconfig.base.json index 496d83ca67..5e84e8105b 100644 --- a/npm/ng-packs/tsconfig.base.json +++ b/npm/ng-packs/tsconfig.base.json @@ -21,6 +21,7 @@ "@abp/ng.account/config": ["packages/account/config/src/public-api.ts"], "@abp/ng.components": ["packages/components/src/public-api.ts"], "@abp/ng.components/chart.js": ["packages/components/chart.js/src/public-api.ts"], + "@abp/ng.components/dynamic-form": ["packages/components/dynamic-form/src/public-api.ts"], "@abp/ng.components/extensible": ["packages/components/extensible/src/public-api.ts"], "@abp/ng.components/lookup": ["packages/components/lookup/src/public-api.ts"], "@abp/ng.components/page": ["packages/components/page/src/public-api.ts"], diff --git a/npm/ng-packs/vitest.config.mts b/npm/ng-packs/vitest.config.mts new file mode 100644 index 0000000000..f239d0bfce --- /dev/null +++ b/npm/ng-packs/vitest.config.mts @@ -0,0 +1,14 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + projects: [ + './packages/core/vitest.config.mts', + './packages/theme-basic/vitest.config.mts', + './packages/theme-shared/vitest.config.mts', + './packages/oauth/vitest.config.mts', + './packages/generators/vitest.config.mts', + './packages/schematics/vitest.config.mts', + ], + }, +}); \ No newline at end of file diff --git a/npm/packs/anchor-js/package.json b/npm/packs/anchor-js/package.json index a724d54f0c..66c70614a8 100644 --- a/npm/packs/anchor-js/package.json +++ b/npm/packs/anchor-js/package.json @@ -1,11 +1,11 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/anchor-js", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~10.1.0-rc.1", + "@abp/core": "~10.1.0-rc.2", "anchor-js": "^5.0.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/aspnetcore.components.server.basictheme/package.json b/npm/packs/aspnetcore.components.server.basictheme/package.json index 70bb2995e3..d566c60e3a 100644 --- a/npm/packs/aspnetcore.components.server.basictheme/package.json +++ b/npm/packs/aspnetcore.components.server.basictheme/package.json @@ -1,11 +1,11 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/aspnetcore.components.server.basictheme", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/aspnetcore.components.server.theming": "~10.1.0-rc.1" + "@abp/aspnetcore.components.server.theming": "~10.1.0-rc.2" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", "homepage": "https://abp.io", diff --git a/npm/packs/aspnetcore.components.server.theming/package.json b/npm/packs/aspnetcore.components.server.theming/package.json index 7a030be39b..5b98884eb5 100644 --- a/npm/packs/aspnetcore.components.server.theming/package.json +++ b/npm/packs/aspnetcore.components.server.theming/package.json @@ -1,12 +1,12 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/aspnetcore.components.server.theming", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/bootstrap": "~10.1.0-rc.1", - "@abp/font-awesome": "~10.1.0-rc.1" + "@abp/bootstrap": "~10.1.0-rc.2", + "@abp/font-awesome": "~10.1.0-rc.2" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", "homepage": "https://abp.io", diff --git a/npm/packs/aspnetcore.mvc.ui.theme.basic/package.json b/npm/packs/aspnetcore.mvc.ui.theme.basic/package.json index f8d0adf403..be30febb4a 100644 --- a/npm/packs/aspnetcore.mvc.ui.theme.basic/package.json +++ b/npm/packs/aspnetcore.mvc.ui.theme.basic/package.json @@ -1,5 +1,5 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/aspnetcore.mvc.ui.theme.basic", "repository": { "type": "git", @@ -10,7 +10,7 @@ "access": "public" }, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.shared": "~10.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.shared": "~10.1.0-rc.2" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", "homepage": "https://abp.io", diff --git a/npm/packs/aspnetcore.mvc.ui.theme.shared/package.json b/npm/packs/aspnetcore.mvc.ui.theme.shared/package.json index 01e5eec485..3143c0fc0a 100644 --- a/npm/packs/aspnetcore.mvc.ui.theme.shared/package.json +++ b/npm/packs/aspnetcore.mvc.ui.theme.shared/package.json @@ -1,5 +1,5 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/aspnetcore.mvc.ui.theme.shared", "repository": { "type": "git", @@ -10,21 +10,21 @@ "access": "public" }, "dependencies": { - "@abp/aspnetcore.mvc.ui": "~10.1.0-rc.1", - "@abp/bootstrap": "~10.1.0-rc.1", - "@abp/bootstrap-datepicker": "~10.1.0-rc.1", - "@abp/bootstrap-daterangepicker": "~10.1.0-rc.1", - "@abp/datatables.net-bs5": "~10.1.0-rc.1", - "@abp/font-awesome": "~10.1.0-rc.1", - "@abp/jquery-form": "~10.1.0-rc.1", - "@abp/jquery-validation-unobtrusive": "~10.1.0-rc.1", - "@abp/lodash": "~10.1.0-rc.1", - "@abp/luxon": "~10.1.0-rc.1", - "@abp/malihu-custom-scrollbar-plugin": "~10.1.0-rc.1", - "@abp/moment": "~10.1.0-rc.1", - "@abp/select2": "~10.1.0-rc.1", - "@abp/sweetalert2": "~10.1.0-rc.1", - "@abp/timeago": "~10.1.0-rc.1" + "@abp/aspnetcore.mvc.ui": "~10.1.0-rc.2", + "@abp/bootstrap": "~10.1.0-rc.2", + "@abp/bootstrap-datepicker": "~10.1.0-rc.2", + "@abp/bootstrap-daterangepicker": "~10.1.0-rc.2", + "@abp/datatables.net-bs5": "~10.1.0-rc.2", + "@abp/font-awesome": "~10.1.0-rc.2", + "@abp/jquery-form": "~10.1.0-rc.2", + "@abp/jquery-validation-unobtrusive": "~10.1.0-rc.2", + "@abp/lodash": "~10.1.0-rc.2", + "@abp/luxon": "~10.1.0-rc.2", + "@abp/malihu-custom-scrollbar-plugin": "~10.1.0-rc.2", + "@abp/moment": "~10.1.0-rc.2", + "@abp/select2": "~10.1.0-rc.2", + "@abp/sweetalert2": "~10.1.0-rc.2", + "@abp/timeago": "~10.1.0-rc.2" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", "homepage": "https://abp.io", diff --git a/npm/packs/aspnetcore.mvc.ui/package-lock.json b/npm/packs/aspnetcore.mvc.ui/package-lock.json index 8f14281b63..3ec3578119 100644 --- a/npm/packs/aspnetcore.mvc.ui/package-lock.json +++ b/npm/packs/aspnetcore.mvc.ui/package-lock.json @@ -1,6 +1,6 @@ { "name": "@abp/aspnetcore.mvc.ui", - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "lockfileVersion": 1, "requires": true, "packages": { diff --git a/npm/packs/aspnetcore.mvc.ui/package.json b/npm/packs/aspnetcore.mvc.ui/package.json index e7eb9e8998..52fe871c52 100644 --- a/npm/packs/aspnetcore.mvc.ui/package.json +++ b/npm/packs/aspnetcore.mvc.ui/package.json @@ -1,5 +1,5 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/aspnetcore.mvc.ui", "repository": { "type": "git", diff --git a/npm/packs/blogging/package.json b/npm/packs/blogging/package.json index 84b9570c6d..94be08cdb9 100644 --- a/npm/packs/blogging/package.json +++ b/npm/packs/blogging/package.json @@ -1,14 +1,14 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/blogging", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.shared": "~10.1.0-rc.1", - "@abp/owl.carousel": "~10.1.0-rc.1", - "@abp/prismjs": "~10.1.0-rc.1", - "@abp/tui-editor": "~10.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.shared": "~10.1.0-rc.2", + "@abp/owl.carousel": "~10.1.0-rc.2", + "@abp/prismjs": "~10.1.0-rc.2", + "@abp/tui-editor": "~10.1.0-rc.2" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", "homepage": "https://abp.io", diff --git a/npm/packs/bootstrap-datepicker/package.json b/npm/packs/bootstrap-datepicker/package.json index ab658627b6..48241bfb07 100644 --- a/npm/packs/bootstrap-datepicker/package.json +++ b/npm/packs/bootstrap-datepicker/package.json @@ -1,5 +1,5 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/bootstrap-datepicker", "repository": { "type": "git", diff --git a/npm/packs/bootstrap-daterangepicker/package.json b/npm/packs/bootstrap-daterangepicker/package.json index c3379798ce..d027c0f0bd 100644 --- a/npm/packs/bootstrap-daterangepicker/package.json +++ b/npm/packs/bootstrap-daterangepicker/package.json @@ -1,5 +1,5 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/bootstrap-daterangepicker", "repository": { "type": "git", diff --git a/npm/packs/bootstrap/package.json b/npm/packs/bootstrap/package.json index 09740ccaaf..479f04b7a1 100644 --- a/npm/packs/bootstrap/package.json +++ b/npm/packs/bootstrap/package.json @@ -1,5 +1,5 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/bootstrap", "repository": { "type": "git", @@ -10,7 +10,7 @@ "access": "public" }, "dependencies": { - "@abp/core": "~10.1.0-rc.1", + "@abp/core": "~10.1.0-rc.2", "bootstrap": "^5.3.8" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/chart.js/package.json b/npm/packs/chart.js/package.json index d50cab9c5a..6f3148b0e3 100644 --- a/npm/packs/chart.js/package.json +++ b/npm/packs/chart.js/package.json @@ -1,5 +1,5 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/chart.js", "publishConfig": { "access": "public" diff --git a/npm/packs/clipboard/package.json b/npm/packs/clipboard/package.json index c0df8a5a7e..64c7810dda 100644 --- a/npm/packs/clipboard/package.json +++ b/npm/packs/clipboard/package.json @@ -1,11 +1,11 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/clipboard", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~10.1.0-rc.1", + "@abp/core": "~10.1.0-rc.2", "clipboard": "^2.0.11" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/cms-kit.admin/package.json b/npm/packs/cms-kit.admin/package.json index dd766d740d..3c9f0b9058 100644 --- a/npm/packs/cms-kit.admin/package.json +++ b/npm/packs/cms-kit.admin/package.json @@ -1,16 +1,16 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/cms-kit.admin", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/codemirror": "~10.1.0-rc.1", - "@abp/jstree": "~10.1.0-rc.1", - "@abp/markdown-it": "~10.1.0-rc.1", - "@abp/slugify": "~10.1.0-rc.1", - "@abp/tui-editor": "~10.1.0-rc.1", - "@abp/uppy": "~10.1.0-rc.1" + "@abp/codemirror": "~10.1.0-rc.2", + "@abp/jstree": "~10.1.0-rc.2", + "@abp/markdown-it": "~10.1.0-rc.2", + "@abp/slugify": "~10.1.0-rc.2", + "@abp/tui-editor": "~10.1.0-rc.2", + "@abp/uppy": "~10.1.0-rc.2" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", "homepage": "https://abp.io", diff --git a/npm/packs/cms-kit.public/package.json b/npm/packs/cms-kit.public/package.json index df6cd51eeb..b7fcf1b540 100644 --- a/npm/packs/cms-kit.public/package.json +++ b/npm/packs/cms-kit.public/package.json @@ -1,12 +1,12 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/cms-kit.public", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/highlight.js": "~10.1.0-rc.1", - "@abp/star-rating-svg": "~10.1.0-rc.1" + "@abp/highlight.js": "~10.1.0-rc.2", + "@abp/star-rating-svg": "~10.1.0-rc.2" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", "homepage": "https://abp.io", diff --git a/npm/packs/cms-kit/package.json b/npm/packs/cms-kit/package.json index b9dfd566f1..86686216d4 100644 --- a/npm/packs/cms-kit/package.json +++ b/npm/packs/cms-kit/package.json @@ -1,12 +1,12 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/cms-kit", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/cms-kit.admin": "~10.1.0-rc.1", - "@abp/cms-kit.public": "~10.1.0-rc.1" + "@abp/cms-kit.admin": "~10.1.0-rc.2", + "@abp/cms-kit.public": "~10.1.0-rc.2" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", "homepage": "https://abp.io", diff --git a/npm/packs/codemirror/package.json b/npm/packs/codemirror/package.json index b99eab22e2..808bc652ef 100644 --- a/npm/packs/codemirror/package.json +++ b/npm/packs/codemirror/package.json @@ -1,11 +1,11 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/codemirror", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~10.1.0-rc.1", + "@abp/core": "~10.1.0-rc.2", "codemirror": "^5.65.1" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/core/package.json b/npm/packs/core/package.json index b0eb886bc2..1b0fea5b69 100644 --- a/npm/packs/core/package.json +++ b/npm/packs/core/package.json @@ -1,5 +1,5 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/core", "repository": { "type": "git", @@ -10,7 +10,7 @@ "access": "public" }, "dependencies": { - "@abp/utils": "~10.1.0-rc.1" + "@abp/utils": "~10.1.0-rc.2" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", "homepage": "https://abp.io", diff --git a/npm/packs/cropperjs/package.json b/npm/packs/cropperjs/package.json index 2854de1d11..8732cf5faf 100644 --- a/npm/packs/cropperjs/package.json +++ b/npm/packs/cropperjs/package.json @@ -1,11 +1,11 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/cropperjs", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~10.1.0-rc.1", + "@abp/core": "~10.1.0-rc.2", "cropperjs": "^1.6.2" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/datatables.net-bs4/package.json b/npm/packs/datatables.net-bs4/package.json index 0926a48ecf..18b00a2cf9 100644 --- a/npm/packs/datatables.net-bs4/package.json +++ b/npm/packs/datatables.net-bs4/package.json @@ -1,5 +1,5 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/datatables.net-bs4", "repository": { "type": "git", @@ -10,7 +10,7 @@ "access": "public" }, "dependencies": { - "@abp/datatables.net": "~10.1.0-rc.1", + "@abp/datatables.net": "~10.1.0-rc.2", "datatables.net-bs4": "^2.3.4" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/datatables.net-bs5/package.json b/npm/packs/datatables.net-bs5/package.json index 123788db08..19c71ea013 100644 --- a/npm/packs/datatables.net-bs5/package.json +++ b/npm/packs/datatables.net-bs5/package.json @@ -1,11 +1,11 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/datatables.net-bs5", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/datatables.net": "~10.1.0-rc.1", + "@abp/datatables.net": "~10.1.0-rc.2", "datatables.net-bs5": "^2.3.4" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/datatables.net/package.json b/npm/packs/datatables.net/package.json index 5bf27eef15..d0f0c240e4 100644 --- a/npm/packs/datatables.net/package.json +++ b/npm/packs/datatables.net/package.json @@ -1,5 +1,5 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/datatables.net", "repository": { "type": "git", @@ -10,7 +10,7 @@ "access": "public" }, "dependencies": { - "@abp/jquery": "~10.1.0-rc.1", + "@abp/jquery": "~10.1.0-rc.2", "datatables.net": "^2.3.4" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/docs/package.json b/npm/packs/docs/package.json index 70cb820497..cafd0f48bb 100644 --- a/npm/packs/docs/package.json +++ b/npm/packs/docs/package.json @@ -1,15 +1,15 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/docs", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/anchor-js": "~10.1.0-rc.1", - "@abp/clipboard": "~10.1.0-rc.1", - "@abp/malihu-custom-scrollbar-plugin": "~10.1.0-rc.1", - "@abp/popper.js": "~10.1.0-rc.1", - "@abp/prismjs": "~10.1.0-rc.1" + "@abp/anchor-js": "~10.1.0-rc.2", + "@abp/clipboard": "~10.1.0-rc.2", + "@abp/malihu-custom-scrollbar-plugin": "~10.1.0-rc.2", + "@abp/popper.js": "~10.1.0-rc.2", + "@abp/prismjs": "~10.1.0-rc.2" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", "homepage": "https://abp.io", diff --git a/npm/packs/flag-icon-css/package.json b/npm/packs/flag-icon-css/package.json index cc9d848dbf..cd5cdec40f 100644 --- a/npm/packs/flag-icon-css/package.json +++ b/npm/packs/flag-icon-css/package.json @@ -1,5 +1,5 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/flag-icon-css", "publishConfig": { "access": "public" diff --git a/npm/packs/flag-icons/package.json b/npm/packs/flag-icons/package.json index d816a32cb8..fba66cb6bb 100644 --- a/npm/packs/flag-icons/package.json +++ b/npm/packs/flag-icons/package.json @@ -1,5 +1,5 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/flag-icons", "publishConfig": { "access": "public" diff --git a/npm/packs/font-awesome/package.json b/npm/packs/font-awesome/package.json index b1e929dfc1..41f0c12daf 100644 --- a/npm/packs/font-awesome/package.json +++ b/npm/packs/font-awesome/package.json @@ -1,5 +1,5 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/font-awesome", "repository": { "type": "git", @@ -10,7 +10,7 @@ "access": "public" }, "dependencies": { - "@abp/core": "~10.1.0-rc.1", + "@abp/core": "~10.1.0-rc.2", "@fortawesome/fontawesome-free": "^7.0.1" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/highlight.js/package.json b/npm/packs/highlight.js/package.json index e1550cd6fa..922abc3da8 100644 --- a/npm/packs/highlight.js/package.json +++ b/npm/packs/highlight.js/package.json @@ -1,11 +1,11 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/highlight.js", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~10.1.0-rc.1", + "@abp/core": "~10.1.0-rc.2", "@highlightjs/cdn-assets": "~11.11.1" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/jquery-form/package.json b/npm/packs/jquery-form/package.json index 89224eb103..9682e9c275 100644 --- a/npm/packs/jquery-form/package.json +++ b/npm/packs/jquery-form/package.json @@ -1,5 +1,5 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/jquery-form", "repository": { "type": "git", @@ -10,7 +10,7 @@ "access": "public" }, "dependencies": { - "@abp/jquery": "~10.1.0-rc.1", + "@abp/jquery": "~10.1.0-rc.2", "jquery-form": "^4.3.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/jquery-validation-unobtrusive/package.json b/npm/packs/jquery-validation-unobtrusive/package.json index 8f97a5f5cc..34ce45bf1c 100644 --- a/npm/packs/jquery-validation-unobtrusive/package.json +++ b/npm/packs/jquery-validation-unobtrusive/package.json @@ -1,5 +1,5 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/jquery-validation-unobtrusive", "repository": { "type": "git", @@ -10,7 +10,7 @@ "access": "public" }, "dependencies": { - "@abp/jquery-validation": "~10.1.0-rc.1", + "@abp/jquery-validation": "~10.1.0-rc.2", "jquery-validation-unobtrusive": "^4.0.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/jquery-validation/package.json b/npm/packs/jquery-validation/package.json index a3191c5fde..4cad463ff2 100644 --- a/npm/packs/jquery-validation/package.json +++ b/npm/packs/jquery-validation/package.json @@ -1,5 +1,5 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/jquery-validation", "repository": { "type": "git", @@ -10,7 +10,7 @@ "access": "public" }, "dependencies": { - "@abp/jquery": "~10.1.0-rc.1", + "@abp/jquery": "~10.1.0-rc.2", "jquery-validation": "^1.21.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/jquery/package.json b/npm/packs/jquery/package.json index ebf031a36a..4a4f7f0aa7 100644 --- a/npm/packs/jquery/package.json +++ b/npm/packs/jquery/package.json @@ -1,5 +1,5 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/jquery", "repository": { "type": "git", @@ -10,7 +10,7 @@ "access": "public" }, "dependencies": { - "@abp/core": "~10.1.0-rc.1", + "@abp/core": "~10.1.0-rc.2", "jquery": "~3.7.1" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/jstree/package.json b/npm/packs/jstree/package.json index 7807ed4a19..24e1786cb7 100644 --- a/npm/packs/jstree/package.json +++ b/npm/packs/jstree/package.json @@ -1,11 +1,11 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/jstree", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/jquery": "~10.1.0-rc.1", + "@abp/jquery": "~10.1.0-rc.2", "jstree": "^3.3.17" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/lodash/package.json b/npm/packs/lodash/package.json index 103906cd55..64050e87fc 100644 --- a/npm/packs/lodash/package.json +++ b/npm/packs/lodash/package.json @@ -1,5 +1,5 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/lodash", "repository": { "type": "git", @@ -10,7 +10,7 @@ "access": "public" }, "dependencies": { - "@abp/core": "~10.1.0-rc.1", + "@abp/core": "~10.1.0-rc.2", "lodash": "^4.17.21" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/luxon/package.json b/npm/packs/luxon/package.json index 5dae9a5a85..29d4dbb782 100644 --- a/npm/packs/luxon/package.json +++ b/npm/packs/luxon/package.json @@ -1,5 +1,5 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/luxon", "repository": { "type": "git", @@ -10,7 +10,7 @@ "access": "public" }, "dependencies": { - "@abp/core": "~10.1.0-rc.1", + "@abp/core": "~10.1.0-rc.2", "luxon": "^3.7.2" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/malihu-custom-scrollbar-plugin/package.json b/npm/packs/malihu-custom-scrollbar-plugin/package.json index 06975481eb..1f291f26ca 100644 --- a/npm/packs/malihu-custom-scrollbar-plugin/package.json +++ b/npm/packs/malihu-custom-scrollbar-plugin/package.json @@ -1,5 +1,5 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/malihu-custom-scrollbar-plugin", "repository": { "type": "git", @@ -10,7 +10,7 @@ "access": "public" }, "dependencies": { - "@abp/core": "~10.1.0-rc.1", + "@abp/core": "~10.1.0-rc.2", "malihu-custom-scrollbar-plugin": "^3.1.5" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/markdown-it/package.json b/npm/packs/markdown-it/package.json index a28b9de901..fb554e3883 100644 --- a/npm/packs/markdown-it/package.json +++ b/npm/packs/markdown-it/package.json @@ -1,11 +1,11 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/markdown-it", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~10.1.0-rc.1", + "@abp/core": "~10.1.0-rc.2", "markdown-it": "^14.1.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/moment/package.json b/npm/packs/moment/package.json index cd16f299d4..b0f2c78e4e 100644 --- a/npm/packs/moment/package.json +++ b/npm/packs/moment/package.json @@ -1,5 +1,5 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/moment", "repository": { "type": "git", diff --git a/npm/packs/owl.carousel/package.json b/npm/packs/owl.carousel/package.json index c309f41281..5044c2fbe0 100644 --- a/npm/packs/owl.carousel/package.json +++ b/npm/packs/owl.carousel/package.json @@ -1,11 +1,11 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/owl.carousel", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~10.1.0-rc.1", + "@abp/core": "~10.1.0-rc.2", "owl.carousel": "^2.3.4" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/popper.js/package.json b/npm/packs/popper.js/package.json index 12b602ce74..cc86b2e2ea 100644 --- a/npm/packs/popper.js/package.json +++ b/npm/packs/popper.js/package.json @@ -1,11 +1,11 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/popper.js", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~10.1.0-rc.1", + "@abp/core": "~10.1.0-rc.2", "@popperjs/core": "^2.11.8" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/prismjs/package.json b/npm/packs/prismjs/package.json index cb6bb52ca2..0e83b34e7f 100644 --- a/npm/packs/prismjs/package.json +++ b/npm/packs/prismjs/package.json @@ -1,12 +1,12 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/prismjs", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/clipboard": "~10.1.0-rc.1", - "@abp/core": "~10.1.0-rc.1", + "@abp/clipboard": "~10.1.0-rc.2", + "@abp/core": "~10.1.0-rc.2", "prismjs": "^1.30.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/qrcode/package.json b/npm/packs/qrcode/package.json index 7c377d613b..ad766dc992 100644 --- a/npm/packs/qrcode/package.json +++ b/npm/packs/qrcode/package.json @@ -1,5 +1,5 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/qrcode", "repository": { "type": "git", @@ -10,7 +10,7 @@ "access": "public" }, "dependencies": { - "@abp/core": "~10.1.0-rc.1" + "@abp/core": "~10.1.0-rc.2" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", "homepage": "https://abp.io", diff --git a/npm/packs/select2/package.json b/npm/packs/select2/package.json index 0debab3ce4..caf990f633 100644 --- a/npm/packs/select2/package.json +++ b/npm/packs/select2/package.json @@ -1,5 +1,5 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/select2", "repository": { "type": "git", @@ -10,7 +10,7 @@ "access": "public" }, "dependencies": { - "@abp/core": "~10.1.0-rc.1", + "@abp/core": "~10.1.0-rc.2", "select2": "^4.0.13" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/signalr/package.json b/npm/packs/signalr/package.json index ae28a9909b..c22f2b18b2 100644 --- a/npm/packs/signalr/package.json +++ b/npm/packs/signalr/package.json @@ -1,11 +1,11 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/signalr", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~10.1.0-rc.1", + "@abp/core": "~10.1.0-rc.2", "@microsoft/signalr": "~9.0.6" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/slugify/package.json b/npm/packs/slugify/package.json index b2cba9a511..9d596d6589 100644 --- a/npm/packs/slugify/package.json +++ b/npm/packs/slugify/package.json @@ -1,5 +1,5 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/slugify", "publishConfig": { "access": "public" diff --git a/npm/packs/star-rating-svg/package.json b/npm/packs/star-rating-svg/package.json index b3d55ca095..00531dcdec 100644 --- a/npm/packs/star-rating-svg/package.json +++ b/npm/packs/star-rating-svg/package.json @@ -1,11 +1,11 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/star-rating-svg", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/jquery": "~10.1.0-rc.1", + "@abp/jquery": "~10.1.0-rc.2", "star-rating-svg": "^3.5.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/sweetalert2/package.json b/npm/packs/sweetalert2/package.json index 1c0109ceb9..236fa38a7e 100644 --- a/npm/packs/sweetalert2/package.json +++ b/npm/packs/sweetalert2/package.json @@ -1,5 +1,5 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/sweetalert2", "publishConfig": { "access": "public" @@ -10,7 +10,7 @@ "directory": "npm/packs/sweetalert2" }, "dependencies": { - "@abp/core": "~10.1.0-rc.1", + "@abp/core": "~10.1.0-rc.2", "sweetalert2": "^11.23.0" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/timeago/package.json b/npm/packs/timeago/package.json index 4f2d1ed933..22f0d3ced5 100644 --- a/npm/packs/timeago/package.json +++ b/npm/packs/timeago/package.json @@ -1,5 +1,5 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/timeago", "repository": { "type": "git", @@ -10,7 +10,7 @@ "access": "public" }, "dependencies": { - "@abp/jquery": "~10.1.0-rc.1", + "@abp/jquery": "~10.1.0-rc.2", "timeago": "^1.6.7" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/toastr/package.json b/npm/packs/toastr/package.json index 68e876d12a..c18fcd70e9 100644 --- a/npm/packs/toastr/package.json +++ b/npm/packs/toastr/package.json @@ -1,5 +1,5 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/toastr", "repository": { "type": "git", @@ -10,7 +10,7 @@ "access": "public" }, "dependencies": { - "@abp/jquery": "~10.1.0-rc.1", + "@abp/jquery": "~10.1.0-rc.2", "toastr": "^2.1.4" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/tui-editor/package.json b/npm/packs/tui-editor/package.json index 326821ed0f..1d18b7aab0 100644 --- a/npm/packs/tui-editor/package.json +++ b/npm/packs/tui-editor/package.json @@ -1,12 +1,12 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/tui-editor", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/jquery": "~10.1.0-rc.1", - "@abp/prismjs": "~10.1.0-rc.1" + "@abp/jquery": "~10.1.0-rc.2", + "@abp/prismjs": "~10.1.0-rc.2" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", "homepage": "https://abp.io", diff --git a/npm/packs/uppy/package.json b/npm/packs/uppy/package.json index ec3d2a1874..6443c16bf9 100644 --- a/npm/packs/uppy/package.json +++ b/npm/packs/uppy/package.json @@ -1,11 +1,11 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/uppy", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~10.1.0-rc.1", + "@abp/core": "~10.1.0-rc.2", "uppy": "^5.1.2" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/utils/package.json b/npm/packs/utils/package.json index 8ff049e666..9a059db2d8 100644 --- a/npm/packs/utils/package.json +++ b/npm/packs/utils/package.json @@ -1,6 +1,6 @@ { "name": "@abp/utils", - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "scripts": { "prepublishOnly": "yarn install --ignore-scripts && node prepublish.js", "ng": "ng", diff --git a/npm/packs/vee-validate/package.json b/npm/packs/vee-validate/package.json index 01e18833a5..30c837454b 100644 --- a/npm/packs/vee-validate/package.json +++ b/npm/packs/vee-validate/package.json @@ -1,11 +1,11 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/vee-validate", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/vue": "~10.1.0-rc.1", + "@abp/vue": "~10.1.0-rc.2", "vee-validate": "~3.4.4" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/npm/packs/virtual-file-explorer/package.json b/npm/packs/virtual-file-explorer/package.json index b7f90f10d4..0672075f5b 100644 --- a/npm/packs/virtual-file-explorer/package.json +++ b/npm/packs/virtual-file-explorer/package.json @@ -1,12 +1,12 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/virtual-file-explorer", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/clipboard": "~10.1.0-rc.1", - "@abp/prismjs": "~10.1.0-rc.1" + "@abp/clipboard": "~10.1.0-rc.2", + "@abp/prismjs": "~10.1.0-rc.2" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", "homepage": "https://abp.io", diff --git a/npm/packs/vue/package.json b/npm/packs/vue/package.json index 448b28d90e..fb878bd9cf 100644 --- a/npm/packs/vue/package.json +++ b/npm/packs/vue/package.json @@ -1,5 +1,5 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/vue", "publishConfig": { "access": "public" diff --git a/npm/packs/zxcvbn/package.json b/npm/packs/zxcvbn/package.json index 1e2d605713..882d8db35f 100644 --- a/npm/packs/zxcvbn/package.json +++ b/npm/packs/zxcvbn/package.json @@ -1,11 +1,11 @@ { - "version": "10.1.0-rc.1", + "version": "10.1.0-rc.2", "name": "@abp/zxcvbn", "publishConfig": { "access": "public" }, "dependencies": { - "@abp/core": "~10.1.0-rc.1", + "@abp/core": "~10.1.0-rc.2", "zxcvbn": "^4.4.2" }, "gitHead": "bb4ea17d5996f01889134c138d00b6c8f858a431", diff --git a/source-code/Volo.Abp.Account.SourceCode/Volo.Abp.Account.SourceCode.zip b/source-code/Volo.Abp.Account.SourceCode/Volo.Abp.Account.SourceCode.zip index 24b416324c..5b64147a89 100644 Binary files a/source-code/Volo.Abp.Account.SourceCode/Volo.Abp.Account.SourceCode.zip and b/source-code/Volo.Abp.Account.SourceCode/Volo.Abp.Account.SourceCode.zip differ diff --git a/source-code/Volo.Abp.AuditLogging.SourceCode/Volo.Abp.AuditLogging.SourceCode.zip b/source-code/Volo.Abp.AuditLogging.SourceCode/Volo.Abp.AuditLogging.SourceCode.zip index ad29ac0b58..0919ab4bab 100644 Binary files a/source-code/Volo.Abp.AuditLogging.SourceCode/Volo.Abp.AuditLogging.SourceCode.zip and b/source-code/Volo.Abp.AuditLogging.SourceCode/Volo.Abp.AuditLogging.SourceCode.zip differ diff --git a/source-code/Volo.Abp.BackgroundJobs.SourceCode/Volo.Abp.BackgroundJobs.SourceCode.zip b/source-code/Volo.Abp.BackgroundJobs.SourceCode/Volo.Abp.BackgroundJobs.SourceCode.zip index af2e3855cc..8322813f0e 100644 Binary files a/source-code/Volo.Abp.BackgroundJobs.SourceCode/Volo.Abp.BackgroundJobs.SourceCode.zip and b/source-code/Volo.Abp.BackgroundJobs.SourceCode/Volo.Abp.BackgroundJobs.SourceCode.zip differ diff --git a/source-code/Volo.Abp.BlobStoring.Database.SourceCode/Volo.Abp.BlobStoring.Database.SourceCode.zip b/source-code/Volo.Abp.BlobStoring.Database.SourceCode/Volo.Abp.BlobStoring.Database.SourceCode.zip index 2bb029f0d9..edac4d8f20 100644 Binary files a/source-code/Volo.Abp.BlobStoring.Database.SourceCode/Volo.Abp.BlobStoring.Database.SourceCode.zip and b/source-code/Volo.Abp.BlobStoring.Database.SourceCode/Volo.Abp.BlobStoring.Database.SourceCode.zip differ diff --git a/source-code/Volo.Abp.FeatureManagement.SourceCode/Volo.Abp.FeatureManagement.SourceCode.zip b/source-code/Volo.Abp.FeatureManagement.SourceCode/Volo.Abp.FeatureManagement.SourceCode.zip index 21ff64871d..fcc3980ea7 100644 Binary files a/source-code/Volo.Abp.FeatureManagement.SourceCode/Volo.Abp.FeatureManagement.SourceCode.zip and b/source-code/Volo.Abp.FeatureManagement.SourceCode/Volo.Abp.FeatureManagement.SourceCode.zip differ diff --git a/source-code/Volo.Abp.Identity.SourceCode/Volo.Abp.Identity.SourceCode.zip b/source-code/Volo.Abp.Identity.SourceCode/Volo.Abp.Identity.SourceCode.zip index 53dde17252..30d07ea91a 100644 Binary files a/source-code/Volo.Abp.Identity.SourceCode/Volo.Abp.Identity.SourceCode.zip and b/source-code/Volo.Abp.Identity.SourceCode/Volo.Abp.Identity.SourceCode.zip differ diff --git a/source-code/Volo.Abp.IdentityServer.SourceCode/Volo.Abp.IdentityServer.SourceCode.zip b/source-code/Volo.Abp.IdentityServer.SourceCode/Volo.Abp.IdentityServer.SourceCode.zip index 17ed9b3e17..19dc79216c 100644 Binary files a/source-code/Volo.Abp.IdentityServer.SourceCode/Volo.Abp.IdentityServer.SourceCode.zip and b/source-code/Volo.Abp.IdentityServer.SourceCode/Volo.Abp.IdentityServer.SourceCode.zip differ diff --git a/source-code/Volo.Abp.OpenIddict.SourceCode/Volo.Abp.OpenIddict.SourceCode.zip b/source-code/Volo.Abp.OpenIddict.SourceCode/Volo.Abp.OpenIddict.SourceCode.zip index 6bbd76d1a8..3a79b434bc 100644 Binary files a/source-code/Volo.Abp.OpenIddict.SourceCode/Volo.Abp.OpenIddict.SourceCode.zip and b/source-code/Volo.Abp.OpenIddict.SourceCode/Volo.Abp.OpenIddict.SourceCode.zip differ diff --git a/source-code/Volo.Abp.PermissionManagement.SourceCode/Volo.Abp.PermissionManagement.SourceCode.zip b/source-code/Volo.Abp.PermissionManagement.SourceCode/Volo.Abp.PermissionManagement.SourceCode.zip index 5418d13391..570d22f3a9 100644 Binary files a/source-code/Volo.Abp.PermissionManagement.SourceCode/Volo.Abp.PermissionManagement.SourceCode.zip and b/source-code/Volo.Abp.PermissionManagement.SourceCode/Volo.Abp.PermissionManagement.SourceCode.zip differ diff --git a/source-code/Volo.Abp.SettingManagement.SourceCode/Volo.Abp.SettingManagement.SourceCode.zip b/source-code/Volo.Abp.SettingManagement.SourceCode/Volo.Abp.SettingManagement.SourceCode.zip index 05d38f535a..469b0b5584 100644 Binary files a/source-code/Volo.Abp.SettingManagement.SourceCode/Volo.Abp.SettingManagement.SourceCode.zip and b/source-code/Volo.Abp.SettingManagement.SourceCode/Volo.Abp.SettingManagement.SourceCode.zip differ diff --git a/source-code/Volo.Abp.TenantManagement.SourceCode/Volo.Abp.TenantManagement.SourceCode.zip b/source-code/Volo.Abp.TenantManagement.SourceCode/Volo.Abp.TenantManagement.SourceCode.zip index ecde418a39..44d7739eff 100644 Binary files a/source-code/Volo.Abp.TenantManagement.SourceCode/Volo.Abp.TenantManagement.SourceCode.zip and b/source-code/Volo.Abp.TenantManagement.SourceCode/Volo.Abp.TenantManagement.SourceCode.zip differ diff --git a/source-code/Volo.Abp.Users.SourceCode/Volo.Abp.Users.SourceCode.zip b/source-code/Volo.Abp.Users.SourceCode/Volo.Abp.Users.SourceCode.zip index 73eb96532b..1925c724ea 100644 Binary files a/source-code/Volo.Abp.Users.SourceCode/Volo.Abp.Users.SourceCode.zip and b/source-code/Volo.Abp.Users.SourceCode/Volo.Abp.Users.SourceCode.zip differ diff --git a/source-code/Volo.Abp.VirtualFileExplorer.SourceCode/Volo.Abp.VirtualFileExplorer.SourceCode.zip b/source-code/Volo.Abp.VirtualFileExplorer.SourceCode/Volo.Abp.VirtualFileExplorer.SourceCode.zip index 14ad32da46..56f1cd2acf 100644 Binary files a/source-code/Volo.Abp.VirtualFileExplorer.SourceCode/Volo.Abp.VirtualFileExplorer.SourceCode.zip and b/source-code/Volo.Abp.VirtualFileExplorer.SourceCode/Volo.Abp.VirtualFileExplorer.SourceCode.zip differ diff --git a/source-code/Volo.Blogging.SourceCode/Volo.Blogging.SourceCode.zip b/source-code/Volo.Blogging.SourceCode/Volo.Blogging.SourceCode.zip index abc34eb4f1..6081d1e39b 100644 Binary files a/source-code/Volo.Blogging.SourceCode/Volo.Blogging.SourceCode.zip and b/source-code/Volo.Blogging.SourceCode/Volo.Blogging.SourceCode.zip differ diff --git a/source-code/Volo.CmsKit.SourceCode/Volo.CmsKit.SourceCode.zip b/source-code/Volo.CmsKit.SourceCode/Volo.CmsKit.SourceCode.zip index 9625f31821..c508876724 100644 Binary files a/source-code/Volo.CmsKit.SourceCode/Volo.CmsKit.SourceCode.zip and b/source-code/Volo.CmsKit.SourceCode/Volo.CmsKit.SourceCode.zip differ diff --git a/source-code/Volo.Docs.SourceCode/Volo.Docs.SourceCode.zip b/source-code/Volo.Docs.SourceCode/Volo.Docs.SourceCode.zip index 54b5da7948..d80581ad13 100644 Binary files a/source-code/Volo.Docs.SourceCode/Volo.Docs.SourceCode.zip and b/source-code/Volo.Docs.SourceCode/Volo.Docs.SourceCode.zip differ diff --git a/templates/app-nolayers/angular/package.json b/templates/app-nolayers/angular/package.json index 93db1446ad..2aadf0e576 100644 --- a/templates/app-nolayers/angular/package.json +++ b/templates/app-nolayers/angular/package.json @@ -12,15 +12,15 @@ }, "private": true, "dependencies": { - "@abp/ng.account": "~10.1.0-rc.1", - "@abp/ng.components": "~10.1.0-rc.1", - "@abp/ng.core": "~10.1.0-rc.1", - "@abp/ng.identity": "~10.1.0-rc.1", - "@abp/ng.oauth": "~10.1.0-rc.1", - "@abp/ng.setting-management": "~10.1.0-rc.1", - "@abp/ng.tenant-management": "~10.1.0-rc.1", - "@abp/ng.theme.lepton-x": "~5.1.0-rc.1", - "@abp/ng.theme.shared": "~10.1.0-rc.1", + "@abp/ng.account": "~10.1.0-rc.2", + "@abp/ng.components": "~10.1.0-rc.2", + "@abp/ng.core": "~10.1.0-rc.2", + "@abp/ng.identity": "~10.1.0-rc.2", + "@abp/ng.oauth": "~10.1.0-rc.2", + "@abp/ng.setting-management": "~10.1.0-rc.2", + "@abp/ng.tenant-management": "~10.1.0-rc.2", + "@abp/ng.theme.lepton-x": "~5.1.0-rc.2", + "@abp/ng.theme.shared": "~10.1.0-rc.2", "@angular/animations": "~21.0.0", "@angular/aria": "~21.0.0", "@angular/common": "~21.0.0", @@ -37,7 +37,7 @@ "zone.js": "~0.15.0" }, "devDependencies": { - "@abp/ng.schematics": "~10.1.0-rc.1", + "@abp/ng.schematics": "~10.1.0-rc.2", "@angular-eslint/builder": "~21.0.0", "@angular-eslint/eslint-plugin": "~21.0.0", "@angular-eslint/eslint-plugin-template": "~21.0.0", diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/package.json b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/package.json index 1d1d350c2a..3369e91103 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/package.json +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server.Mongo/package.json @@ -3,7 +3,7 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.components.server.leptonxlitetheme": "~5.1.0-rc.1", - "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.1.0-rc.1" + "@abp/aspnetcore.components.server.leptonxlitetheme": "~5.1.0-rc.2", + "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.1.0-rc.2" } } diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/package.json b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/package.json index 63723159e9..05b853bd30 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/package.json +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.Server/package.json @@ -3,7 +3,7 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.1.0-rc.1", - "@abp/aspnetcore.components.server.leptonxlitetheme": "~5.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.1.0-rc.2", + "@abp/aspnetcore.components.server.leptonxlitetheme": "~5.1.0-rc.2" } } diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/package.json b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/package.json index b3eb9cb472..ca7fd1e83f 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/package.json +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server.Mongo/package.json @@ -3,6 +3,6 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.1.0-rc.2" } } diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/package.json b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/package.json index b3eb9cb472..ca7fd1e83f 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/package.json +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Blazor.WebAssembly/Server/package.json @@ -3,6 +3,6 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.1.0-rc.2" } } diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/package.json b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/package.json index b3eb9cb472..ca7fd1e83f 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/package.json +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host.Mongo/package.json @@ -3,6 +3,6 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.1.0-rc.2" } } diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/package.json b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/package.json index b3eb9cb472..ca7fd1e83f 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/package.json +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Host/package.json @@ -3,6 +3,6 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.1.0-rc.2" } } diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/package.json b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/package.json index b3eb9cb472..ca7fd1e83f 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/package.json +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc.Mongo/package.json @@ -3,6 +3,6 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.1.0-rc.2" } } diff --git a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/package.json b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/package.json index b3eb9cb472..ca7fd1e83f 100644 --- a/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/package.json +++ b/templates/app-nolayers/aspnet-core/MyCompanyName.MyProjectName.Mvc/package.json @@ -3,6 +3,6 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.1.0-rc.2" } } diff --git a/templates/app/angular/package.json b/templates/app/angular/package.json index 9710a598f6..9b318eb19b 100644 --- a/templates/app/angular/package.json +++ b/templates/app/angular/package.json @@ -12,15 +12,15 @@ }, "private": true, "dependencies": { - "@abp/ng.account": "~10.1.0-rc.1", - "@abp/ng.components": "~10.1.0-rc.1", - "@abp/ng.core": "~10.1.0-rc.1", - "@abp/ng.identity": "~10.1.0-rc.1", - "@abp/ng.oauth": "~10.1.0-rc.1", - "@abp/ng.setting-management": "~10.1.0-rc.1", - "@abp/ng.tenant-management": "~10.1.0-rc.1", - "@abp/ng.theme.lepton-x": "~5.1.0-rc.1", - "@abp/ng.theme.shared": "~10.1.0-rc.1", + "@abp/ng.account": "~10.1.0-rc.2", + "@abp/ng.components": "~10.1.0-rc.2", + "@abp/ng.core": "~10.1.0-rc.2", + "@abp/ng.identity": "~10.1.0-rc.2", + "@abp/ng.oauth": "~10.1.0-rc.2", + "@abp/ng.setting-management": "~10.1.0-rc.2", + "@abp/ng.tenant-management": "~10.1.0-rc.2", + "@abp/ng.theme.lepton-x": "~5.1.0-rc.2", + "@abp/ng.theme.shared": "~10.1.0-rc.2", "@angular/animations": "~21.0.0", "@angular/aria": "~21.0.0", "@angular/common": "~21.0.0", @@ -37,7 +37,7 @@ "zone.js": "~0.15.0" }, "devDependencies": { - "@abp/ng.schematics": "~10.1.0-rc.1", + "@abp/ng.schematics": "~10.1.0-rc.2", "@angular-eslint/builder": "~21.0.0", "@angular-eslint/eslint-plugin": "~21.0.0", "@angular-eslint/eslint-plugin-template": "~21.0.0", diff --git a/templates/app/angular/src/app/home/home.component.html b/templates/app/angular/src/app/home/home.component.html index e3e73e48ae..edcdc66bc0 100644 --- a/templates/app/angular/src/app/home/home.component.html +++ b/templates/app/angular/src/app/home/home.component.html @@ -1,4 +1,5 @@
+
diff --git a/templates/app/angular/src/app/home/home.component.ts b/templates/app/angular/src/app/home/home.component.ts index 420edd4724..ee6bd838c9 100644 --- a/templates/app/angular/src/app/home/home.component.ts +++ b/templates/app/angular/src/app/home/home.component.ts @@ -1,16 +1,89 @@ import {AuthService, LocalizationPipe} from '@abp/ng.core'; import { Component, inject } from '@angular/core'; import {NgTemplateOutlet} from "@angular/common"; +import {DynamicFormComponent, FormFieldConfig} from "@abp/ng.components/dynamic-form"; @Component({ selector: 'app-home', templateUrl: './home.component.html', styleUrls: ['./home.component.scss'], - imports: [NgTemplateOutlet, LocalizationPipe] + imports: [NgTemplateOutlet, LocalizationPipe, DynamicFormComponent] }) export class HomeComponent { private authService = inject(AuthService); + formFields: FormFieldConfig[] = [ + { + key: 'firstName', + type: 'text', + label: 'First Name', + placeholder: 'Enter first name', + value: 'erdemc', + required: true, + validators: [ + { type: 'required', message: 'First name is required' }, + { type: 'minLength', value: 2, message: 'Minimum 2 characters required' } + ], + gridSize: 6, + order: 1 + }, + { + key: 'lastName', + type: 'text', + label: 'Last Name', + placeholder: 'Enter last name', + required: true, + validators: [ + { type: 'required', message: 'Last name is required' } + ], + gridSize: 12, + order: 3 + }, + { + key: 'email', + type: 'email', + label: 'Email Address', + placeholder: 'Enter email', + required: true, + validators: [ + { type: 'required', message: 'Email is required' }, + { type: 'email', message: 'Please enter a valid email' } + ], + gridSize: 6, + order: 2 + }, + { + key: 'userType', + type: 'select', + label: 'User Type', + required: true, + options: [ + { key: 'admin', value: 'Administrator' }, + { key: 'user', value: 'Regular User' }, + { key: 'guest', value: 'Guest User' } + ], + validators: [ + { type: 'required', message: 'Please select user type' } + ], + order: 4 + }, + { + key: 'adminNotes', + type: 'textarea', + label: 'Admin Notes', + placeholder: 'Enter admin-specific notes', + conditionalLogic: [ + { + dependsOn: 'userType', + condition: 'equals', + value: 'admin', + action: 'show' + } + ], + order: 5 + } + ]; + get hasLoggedIn(): boolean { return this.authService.isAuthenticated; } diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.AuthServer/package.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.AuthServer/package.json index 3038ea90ab..f8c0f62c92 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.AuthServer/package.json +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.AuthServer/package.json @@ -3,6 +3,6 @@ "name": "my-app-authserver", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.1.0-rc.2" } } diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/package.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/package.json index 63723159e9..05b853bd30 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/package.json +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server.Tiered/package.json @@ -3,7 +3,7 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.1.0-rc.1", - "@abp/aspnetcore.components.server.leptonxlitetheme": "~5.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.1.0-rc.2", + "@abp/aspnetcore.components.server.leptonxlitetheme": "~5.1.0-rc.2" } } diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/package.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/package.json index 63723159e9..05b853bd30 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/package.json +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.Server/package.json @@ -3,7 +3,7 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.1.0-rc.1", - "@abp/aspnetcore.components.server.leptonxlitetheme": "~5.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.1.0-rc.2", + "@abp/aspnetcore.components.server.leptonxlitetheme": "~5.1.0-rc.2" } } diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/package.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/package.json index 63723159e9..05b853bd30 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/package.json +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp.Tiered/package.json @@ -3,7 +3,7 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.1.0-rc.1", - "@abp/aspnetcore.components.server.leptonxlitetheme": "~5.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.1.0-rc.2", + "@abp/aspnetcore.components.server.leptonxlitetheme": "~5.1.0-rc.2" } } diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/package.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/package.json index 63723159e9..05b853bd30 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/package.json +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Blazor.WebApp/package.json @@ -3,7 +3,7 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.1.0-rc.1", - "@abp/aspnetcore.components.server.leptonxlitetheme": "~5.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.1.0-rc.2", + "@abp/aspnetcore.components.server.leptonxlitetheme": "~5.1.0-rc.2" } } diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/package.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/package.json index b3eb9cb472..ca7fd1e83f 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/package.json +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.HostWithIds/package.json @@ -3,6 +3,6 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.1.0-rc.2" } } diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/package.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/package.json index b3eb9cb472..ca7fd1e83f 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/package.json +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/package.json @@ -3,6 +3,6 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.1.0-rc.2" } } diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/package.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/package.json index b3eb9cb472..ca7fd1e83f 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/package.json +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web/package.json @@ -3,6 +3,6 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.leptonxlite": "~5.1.0-rc.2" } } diff --git a/templates/module/angular/package.json b/templates/module/angular/package.json index b386586560..61e301fad5 100644 --- a/templates/module/angular/package.json +++ b/templates/module/angular/package.json @@ -13,15 +13,15 @@ }, "private": true, "dependencies": { - "@abp/ng.account": "~10.1.0-rc.1", - "@abp/ng.components": "~10.1.0-rc.1", - "@abp/ng.core": "~10.1.0-rc.1", - "@abp/ng.identity": "~10.1.0-rc.1", - "@abp/ng.oauth": "~10.1.0-rc.1", - "@abp/ng.setting-management": "~10.1.0-rc.1", - "@abp/ng.tenant-management": "~10.1.0-rc.1", - "@abp/ng.theme.basic": "~10.1.0-rc.1", - "@abp/ng.theme.shared": "~10.1.0-rc.1", + "@abp/ng.account": "~10.1.0-rc.2", + "@abp/ng.components": "~10.1.0-rc.2", + "@abp/ng.core": "~10.1.0-rc.2", + "@abp/ng.identity": "~10.1.0-rc.2", + "@abp/ng.oauth": "~10.1.0-rc.2", + "@abp/ng.setting-management": "~10.1.0-rc.2", + "@abp/ng.tenant-management": "~10.1.0-rc.2", + "@abp/ng.theme.basic": "~10.1.0-rc.2", + "@abp/ng.theme.shared": "~10.1.0-rc.2", "@angular/animations": "~21.0.0", "@angular/aria": "~21.0.0", "@angular/common": "~21.0.0", @@ -37,7 +37,7 @@ "zone.js": "~0.15.0" }, "devDependencies": { - "@abp/ng.schematics": "~10.1.0-rc.1", + "@abp/ng.schematics": "~10.1.0-rc.2", "@angular-eslint/builder": "~21.0.0", "@angular-eslint/eslint-plugin": "~21.0.0", "@angular-eslint/eslint-plugin-template": "~21.0.0", diff --git a/templates/module/angular/projects/my-project-name/package.json b/templates/module/angular/projects/my-project-name/package.json index aa4b0f95e3..5b2413bd3d 100644 --- a/templates/module/angular/projects/my-project-name/package.json +++ b/templates/module/angular/projects/my-project-name/package.json @@ -4,8 +4,8 @@ "peerDependencies": { "@angular/common": "~19.1.0", "@angular/core": "~19.1.0", - "@abp/ng.core": "~10.1.0-rc.1", - "@abp/ng.theme.shared": "~10.1.0-rc.1" + "@abp/ng.core": "~10.1.0-rc.2", + "@abp/ng.theme.shared": "~10.1.0-rc.2" }, "dependencies": { "tslib": "^2.1.0" diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/package.json b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/package.json index a437271d58..214f77a570 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/package.json +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.AuthServer/package.json @@ -3,6 +3,6 @@ "name": "my-app-authserver", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~10.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.basic": "~10.1.0-rc.2" } } diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/package.json b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/package.json index c4d308a9c7..11a6338595 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/package.json +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Blazor.Server.Host/package.json @@ -3,7 +3,7 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~10.1.0-rc.1", - "@abp/aspnetcore.components.server.basictheme": "~10.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.basic": "~10.1.0-rc.2", + "@abp/aspnetcore.components.server.basictheme": "~10.1.0-rc.2" } } diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/package.json b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/package.json index 881359366e..52a1fc6c21 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/package.json +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Host/package.json @@ -3,6 +3,6 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~10.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.basic": "~10.1.0-rc.2" } } diff --git a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/package.json b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/package.json index 881359366e..52a1fc6c21 100644 --- a/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/package.json +++ b/templates/module/aspnet-core/host/MyCompanyName.MyProjectName.Web.Unified/package.json @@ -3,6 +3,6 @@ "name": "my-app", "private": true, "dependencies": { - "@abp/aspnetcore.mvc.ui.theme.basic": "~10.1.0-rc.1" + "@abp/aspnetcore.mvc.ui.theme.basic": "~10.1.0-rc.2" } }