mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
♻️ refactor(claude): migrate skills to new skill-creator format
Recreate all 11 skills using the updated skill-creator tooling: - api-sync, arch-docs, architecture-validator, css-animations - git-workflow, library-creator, logging, state-patterns - tailwind, template-standards, test-migration Key changes: - Updated YAML frontmatter structure - Reorganized logging references into references/ subdirectory - Applied progressive disclosure patterns where applicable
This commit is contained in:
@@ -1,371 +1,362 @@
|
||||
---
|
||||
name: api-sync
|
||||
description: This skill should be used when regenerating Swagger/OpenAPI TypeScript API clients with breaking change detection. Handles generation of all 10 API clients (or specific ones), pre-generation impact analysis, Unicode cleanup, TypeScript validation, and affected test execution. Use when user requests "API sync", "regenerate swagger", "check breaking changes", or indicates backend API changes.
|
||||
---
|
||||
|
||||
# API Sync Manager
|
||||
|
||||
## Overview
|
||||
|
||||
Automate the complete lifecycle of TypeScript API client regeneration from Swagger/OpenAPI specifications. Provides pre-generation breaking change detection, automatic post-processing, impact analysis, validation, and migration recommendations for all 10 API clients in the ISA-Frontend monorepo.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Invoke when user requests:
|
||||
- API client regeneration or sync
|
||||
- "Check breaking changes" before API update
|
||||
- Backend API changes need frontend updates
|
||||
- Impact assessment of API changes
|
||||
- "Regenerate swagger" or "update API clients"
|
||||
|
||||
## Available APIs
|
||||
|
||||
availability-api, cat-search-api, checkout-api, crm-api, eis-api, inventory-api, isa-api, oms-api, print-api, wws-api
|
||||
|
||||
## Unified Sync Workflow
|
||||
|
||||
### Step 1: Pre-Generation Check
|
||||
|
||||
```bash
|
||||
# Check uncommitted changes
|
||||
git status generated/swagger/
|
||||
|
||||
# Verify no manual edits will be lost
|
||||
git diff generated/swagger/
|
||||
```
|
||||
|
||||
If uncommitted changes exist, warn user and ask to proceed. If manual edits detected, strongly recommend committing first.
|
||||
|
||||
### Step 2: Pre-Generation Breaking Change Detection
|
||||
|
||||
Generate to temporary location to compare without affecting working directory:
|
||||
|
||||
```bash
|
||||
# Backup current state
|
||||
cp -r generated/swagger/[api-name] /tmp/[api-name].backup
|
||||
|
||||
# Generate to temp location for comparison
|
||||
npm run generate:swagger:[api-name]
|
||||
```
|
||||
|
||||
**Compare Models and Services:**
|
||||
|
||||
```bash
|
||||
diff -u /tmp/[api-name].backup/models.ts generated/swagger/[api-name]/models.ts
|
||||
diff -u /tmp/[api-name].backup/services.ts generated/swagger/[api-name]/services.ts
|
||||
```
|
||||
|
||||
**Categorize Changes:**
|
||||
|
||||
**🔴 Breaking (Critical):**
|
||||
- Removed properties from response models
|
||||
- Changed property types (string → number, object → array)
|
||||
- Removed endpoints
|
||||
- Optional → required fields in request models
|
||||
- Removed enum values
|
||||
- Changed endpoint paths or HTTP methods
|
||||
|
||||
**⚠️ Warnings (Review Required):**
|
||||
- Property renamed (old removed + new added)
|
||||
- Changed default values
|
||||
- Changed validation rules (min/max length, pattern)
|
||||
- Added required request fields
|
||||
- Changed parameter locations (query → body)
|
||||
|
||||
**✅ Compatible (Safe):**
|
||||
- Added properties to response models
|
||||
- New endpoints
|
||||
- Added optional parameters
|
||||
- New enum values
|
||||
- Required → optional fields
|
||||
|
||||
### Step 3: Impact Analysis
|
||||
|
||||
For each breaking or warning change, analyze codebase impact:
|
||||
|
||||
```bash
|
||||
# Find all imports from affected API
|
||||
grep -r "from '@generated/swagger/[api-name]" libs/ --include="*.ts"
|
||||
|
||||
# Find usages of specific removed/changed items
|
||||
grep -r "[RemovedType|removedProperty|removedMethod]" libs/*/data-access --include="*.ts"
|
||||
```
|
||||
|
||||
**Document:**
|
||||
- Affected files (with line numbers)
|
||||
- Services impacted
|
||||
- Components/stores using affected services
|
||||
- Estimated refactoring effort (hours)
|
||||
|
||||
### Step 4: Generate Migration Strategy
|
||||
|
||||
Based on breaking change severity:
|
||||
|
||||
**High Impact (>5 breaking changes or critical endpoints):**
|
||||
1. Create dedicated migration branch from develop
|
||||
2. Document all changes in migration guide
|
||||
3. Update data-access services incrementally
|
||||
4. Update affected stores and components
|
||||
5. Comprehensive test coverage
|
||||
6. Coordinate deployment with backend team
|
||||
|
||||
**Medium Impact (2-5 breaking changes):**
|
||||
1. Fix TypeScript compilation errors
|
||||
2. Update affected data-access services
|
||||
3. Update tests
|
||||
4. Run affected test suite
|
||||
5. Deploy with monitoring
|
||||
|
||||
**Low Impact (1 breaking change, minor impact):**
|
||||
1. Apply minimal updates
|
||||
2. Verify tests pass
|
||||
3. Deploy normally
|
||||
|
||||
### Step 5: Execute Generation
|
||||
|
||||
If user approves proceeding after impact analysis:
|
||||
|
||||
```bash
|
||||
# All APIs
|
||||
npm run generate:swagger
|
||||
|
||||
# Specific API (if api-name provided)
|
||||
npm run generate:swagger:[api-name]
|
||||
```
|
||||
|
||||
### Step 6: Verify Unicode Cleanup
|
||||
|
||||
Automatic cleanup via `tools/fix-files.js` should execute during generation. Verify:
|
||||
|
||||
```bash
|
||||
# Scan for remaining Unicode issues
|
||||
grep -r "\\\\u00" generated/swagger/ || echo "✅ No Unicode issues"
|
||||
```
|
||||
|
||||
If issues remain, run manually:
|
||||
|
||||
```bash
|
||||
node tools/fix-files.js
|
||||
```
|
||||
|
||||
### Step 7: TypeScript Validation
|
||||
|
||||
```bash
|
||||
# Full TypeScript compilation check
|
||||
npx tsc --noEmit
|
||||
|
||||
# If errors, show affected files
|
||||
npx tsc --noEmit | head -20
|
||||
```
|
||||
|
||||
Document compilation errors:
|
||||
- Missing properties
|
||||
- Type mismatches
|
||||
- Incompatible signatures
|
||||
|
||||
### Step 8: Run Affected Tests
|
||||
|
||||
```bash
|
||||
# Run tests for affected projects
|
||||
npx nx affected:test --skip-nx-cache --base=HEAD~1
|
||||
|
||||
# Lint affected projects
|
||||
npx nx affected:lint --base=HEAD~1
|
||||
```
|
||||
|
||||
Monitor test failures and categorize:
|
||||
- Mock data mismatches (update fixtures)
|
||||
- Type assertion failures (update test types)
|
||||
- Integration test failures (API contract changes)
|
||||
|
||||
### Step 9: Generate Comprehensive Report
|
||||
|
||||
```
|
||||
API Sync Manager Report
|
||||
=======================
|
||||
|
||||
API: [api-name | all]
|
||||
Sync Date: [timestamp]
|
||||
Generation: ✅ Success / ❌ Failed
|
||||
|
||||
📊 Change Summary
|
||||
-----------------
|
||||
Breaking Changes: XX
|
||||
Warnings: XX
|
||||
Compatible Changes: XX
|
||||
Files Modified: XX
|
||||
|
||||
🔴 Breaking Changes
|
||||
-------------------
|
||||
1. [API Name] - Removed Property: OrderResponse.deliveryDate
|
||||
Impact: Medium (2 files affected)
|
||||
Files:
|
||||
- libs/oms/data-access/src/lib/services/order.service.ts:45
|
||||
- libs/oms/feature/order-detail/src/lib/component.ts:78
|
||||
Fix Strategy: Remove references or use alternativeDate field
|
||||
Estimated Effort: 30 minutes
|
||||
|
||||
2. [API Name] - Type Changed: ProductResponse.price (string → number)
|
||||
Impact: High (1 file + cascading changes)
|
||||
Files:
|
||||
- libs/catalogue/data-access/src/lib/services/product.service.ts:32
|
||||
Fix Strategy: Remove string parsing, update price calculations
|
||||
Estimated Effort: 1 hour
|
||||
|
||||
⚠️ Warnings (Review Required)
|
||||
------------------------------
|
||||
1. [API Name] - Possible Rename: CustomerResponse.customerName → fullName
|
||||
Action: Verify with backend team if intentional
|
||||
Migration: Update references if confirmed rename
|
||||
|
||||
2. [API Name] - New Required Field: CreateOrderRequest.taxId
|
||||
Action: Update order creation flows to provide taxId
|
||||
Impact: 3 order creation forms
|
||||
|
||||
✅ Compatible Changes
|
||||
---------------------
|
||||
1. [API Name] - Added Property: OrderResponse.estimatedDelivery
|
||||
2. [API Name] - New Endpoint: GET /api/v2/orders/bulk
|
||||
3. [API Name] - Added Optional Parameter: includeArchived to GET /orders
|
||||
|
||||
📊 Validation Results
|
||||
---------------------
|
||||
TypeScript Compilation: ✅ Pass / ❌ XX errors
|
||||
Unicode Cleanup: ✅ Complete
|
||||
Tests: XX/XX passing (YY affected)
|
||||
Lint: ✅ Pass / ⚠️ XX warnings
|
||||
|
||||
❌ Test Failures (if any)
|
||||
-------------------------
|
||||
1. order.service.spec.ts - Mock response missing deliveryDate
|
||||
Fix: Update mock fixture
|
||||
2. product.component.spec.ts - Price type assertion failed
|
||||
Fix: Change expect(price).toBe("10.99") → expect(price).toBe(10.99)
|
||||
|
||||
💡 Migration Strategy
|
||||
---------------------
|
||||
Approach: [High/Medium/Low Impact]
|
||||
Estimated Total Effort: [hours]
|
||||
|
||||
Steps:
|
||||
1. [Fix compilation errors in data-access services]
|
||||
2. [Update test mocks and fixtures]
|
||||
3. [Update components using affected properties]
|
||||
4. [Run full test suite]
|
||||
5. [Deploy with backend coordination]
|
||||
|
||||
🎯 Recommendation
|
||||
-----------------
|
||||
[One of the following:]
|
||||
✅ Safe to proceed - Only compatible changes detected
|
||||
⚠️ Proceed with caution - Fix breaking changes before deployment
|
||||
🔴 Coordinate with backend - High impact changes require migration plan
|
||||
🛑 Block regeneration - Critical breaking changes, backend rollback needed
|
||||
|
||||
📋 Next Steps
|
||||
-------------
|
||||
[Specific actions user should take]
|
||||
- [ ] Fix compilation errors in [files]
|
||||
- [ ] Update test mocks in [files]
|
||||
- [ ] Coordinate deployment timing with backend team
|
||||
- [ ] Monitor [specific endpoints] after deployment
|
||||
```
|
||||
|
||||
### Step 10: Cleanup
|
||||
|
||||
```bash
|
||||
# Remove temporary backup
|
||||
rm -rf /tmp/[api-name].backup
|
||||
|
||||
# Or restore if generation needs to be reverted
|
||||
# cp -r /tmp/[api-name].backup generated/swagger/[api-name]
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
**Generation Fails:**
|
||||
- Check OpenAPI spec URLs in package.json scripts
|
||||
- Verify backend API is accessible
|
||||
- Check for malformed OpenAPI specification
|
||||
|
||||
**Unicode Cleanup Fails:**
|
||||
- Run `node tools/fix-files.js` manually
|
||||
- Check for new Unicode patterns not covered by script
|
||||
|
||||
**TypeScript Compilation Errors:**
|
||||
- Review breaking changes section in report
|
||||
- Update affected data-access services first
|
||||
- Fix cascading type errors in consumers
|
||||
|
||||
**Test Failures:**
|
||||
- Update mock data to match new API contracts
|
||||
- Fix type assertions in tests
|
||||
- Update integration test expectations
|
||||
|
||||
**Diff Detection Issues:**
|
||||
- Ensure clean git state before running
|
||||
- Check file permissions on generated/ directory
|
||||
- Verify backup location is writable
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
**Compare Specific Endpoints Only:**
|
||||
|
||||
```bash
|
||||
# Extract and compare specific interface
|
||||
grep -A 20 "interface OrderResponse" /tmp/[api-name].backup/models.ts
|
||||
grep -A 20 "interface OrderResponse" generated/swagger/[api-name]/models.ts
|
||||
```
|
||||
|
||||
**Bulk API Sync with Change Detection:**
|
||||
|
||||
Run pre-generation detection for all APIs before regenerating:
|
||||
|
||||
```bash
|
||||
for api in availability-api cat-search-api checkout-api crm-api eis-api inventory-api isa-api oms-api print-api wws-api; do
|
||||
echo "Checking $api..."
|
||||
# Run detection workflow for each
|
||||
done
|
||||
```
|
||||
|
||||
Generate consolidated report across all APIs before committing.
|
||||
|
||||
**Rollback Procedure:**
|
||||
|
||||
```bash
|
||||
# If sync causes critical issues
|
||||
git checkout generated/swagger/[api-name]
|
||||
git clean -fd generated/swagger/[api-name]
|
||||
|
||||
# Or restore from backup
|
||||
cp -r generated/swagger.backup.[timestamp]/* generated/swagger/
|
||||
```
|
||||
|
||||
## Integration with Git Workflow
|
||||
|
||||
**Recommended Commit Message:**
|
||||
|
||||
```
|
||||
feat(api): sync [api-name] API client [TASK-####]
|
||||
|
||||
Breaking changes:
|
||||
- Removed OrderResponse.deliveryDate (use estimatedDelivery)
|
||||
- Changed ProductResponse.price type (string → number)
|
||||
|
||||
Compatible changes:
|
||||
- Added OrderResponse.estimatedDelivery
|
||||
- New endpoint GET /api/v2/orders/bulk
|
||||
```
|
||||
|
||||
**Branch Strategy:**
|
||||
|
||||
- Low impact: Commit to feature branch directly
|
||||
- Medium impact: Create `sync/[api-name]-[date]` branch
|
||||
- High impact: Create `migration/[api-name]-[version]` branch with migration guide
|
||||
|
||||
## References
|
||||
|
||||
- CLAUDE.md API Integration section
|
||||
- package.json swagger generation scripts
|
||||
- tools/fix-files.js for Unicode cleanup logic
|
||||
- Semantic Versioning: https://semver.org
|
||||
---
|
||||
name: api-sync
|
||||
description: This skill should be used when regenerating Swagger/OpenAPI TypeScript API clients with breaking change detection. Handles generation of all 10 API clients (or specific ones), pre-generation impact analysis, Unicode cleanup, TypeScript validation, and affected test execution. Use when user requests "API sync", "regenerate swagger", "check breaking changes", or indicates backend API changes.
|
||||
---
|
||||
|
||||
# API Sync Manager
|
||||
|
||||
## Overview
|
||||
|
||||
Automate the complete lifecycle of TypeScript API client regeneration from Swagger/OpenAPI specifications. Provides pre-generation breaking change detection, automatic post-processing, impact analysis, validation, and migration recommendations for all 10 API clients in the ISA-Frontend monorepo.
|
||||
|
||||
## Available APIs
|
||||
|
||||
availability-api, cat-search-api, checkout-api, crm-api, eis-api, inventory-api, isa-api, oms-api, print-api, wws-api
|
||||
|
||||
## Unified Sync Workflow
|
||||
|
||||
### Step 1: Pre-Generation Check
|
||||
|
||||
```bash
|
||||
# Check uncommitted changes
|
||||
git status generated/swagger/
|
||||
|
||||
# Verify no manual edits will be lost
|
||||
git diff generated/swagger/
|
||||
```
|
||||
|
||||
If uncommitted changes exist, warn user and ask to proceed. If manual edits detected, strongly recommend committing first.
|
||||
|
||||
### Step 2: Pre-Generation Breaking Change Detection
|
||||
|
||||
Generate to temporary location to compare without affecting working directory:
|
||||
|
||||
```bash
|
||||
# Backup current state
|
||||
cp -r generated/swagger/[api-name] /tmp/[api-name].backup
|
||||
|
||||
# Generate to temp location for comparison
|
||||
npm run generate:swagger:[api-name]
|
||||
```
|
||||
|
||||
**Compare Models and Services:**
|
||||
|
||||
```bash
|
||||
diff -u /tmp/[api-name].backup/models.ts generated/swagger/[api-name]/models.ts
|
||||
diff -u /tmp/[api-name].backup/services.ts generated/swagger/[api-name]/services.ts
|
||||
```
|
||||
|
||||
**Categorize Changes:**
|
||||
|
||||
**🔴 Breaking (Critical):**
|
||||
- Removed properties from response models
|
||||
- Changed property types (string → number, object → array)
|
||||
- Removed endpoints
|
||||
- Optional → required fields in request models
|
||||
- Removed enum values
|
||||
- Changed endpoint paths or HTTP methods
|
||||
|
||||
**⚠️ Warnings (Review Required):**
|
||||
- Property renamed (old removed + new added)
|
||||
- Changed default values
|
||||
- Changed validation rules (min/max length, pattern)
|
||||
- Added required request fields
|
||||
- Changed parameter locations (query → body)
|
||||
|
||||
**✅ Compatible (Safe):**
|
||||
- Added properties to response models
|
||||
- New endpoints
|
||||
- Added optional parameters
|
||||
- New enum values
|
||||
- Required → optional fields
|
||||
|
||||
### Step 3: Impact Analysis
|
||||
|
||||
For each breaking or warning change, analyze codebase impact:
|
||||
|
||||
```bash
|
||||
# Find all imports from affected API
|
||||
grep -r "from '@generated/swagger/[api-name]" libs/ --include="*.ts"
|
||||
|
||||
# Find usages of specific removed/changed items
|
||||
grep -r "[RemovedType|removedProperty|removedMethod]" libs/*/data-access --include="*.ts"
|
||||
```
|
||||
|
||||
**Document:**
|
||||
- Affected files (with line numbers)
|
||||
- Services impacted
|
||||
- Components/stores using affected services
|
||||
- Estimated refactoring effort (hours)
|
||||
|
||||
### Step 4: Generate Migration Strategy
|
||||
|
||||
Based on breaking change severity:
|
||||
|
||||
**High Impact (>5 breaking changes or critical endpoints):**
|
||||
1. Create dedicated migration branch from develop
|
||||
2. Document all changes in migration guide
|
||||
3. Update data-access services incrementally
|
||||
4. Update affected stores and components
|
||||
5. Comprehensive test coverage
|
||||
6. Coordinate deployment with backend team
|
||||
|
||||
**Medium Impact (2-5 breaking changes):**
|
||||
1. Fix TypeScript compilation errors
|
||||
2. Update affected data-access services
|
||||
3. Update tests
|
||||
4. Run affected test suite
|
||||
5. Deploy with monitoring
|
||||
|
||||
**Low Impact (1 breaking change, minor impact):**
|
||||
1. Apply minimal updates
|
||||
2. Verify tests pass
|
||||
3. Deploy normally
|
||||
|
||||
### Step 5: Execute Generation
|
||||
|
||||
If user approves proceeding after impact analysis:
|
||||
|
||||
```bash
|
||||
# All APIs
|
||||
npm run generate:swagger
|
||||
|
||||
# Specific API (if api-name provided)
|
||||
npm run generate:swagger:[api-name]
|
||||
```
|
||||
|
||||
### Step 6: Verify Unicode Cleanup
|
||||
|
||||
Automatic cleanup via `tools/fix-files.js` should execute during generation. Verify:
|
||||
|
||||
```bash
|
||||
# Scan for remaining Unicode issues
|
||||
grep -r "\\\\u00" generated/swagger/ || echo "✅ No Unicode issues"
|
||||
```
|
||||
|
||||
If issues remain, run manually:
|
||||
|
||||
```bash
|
||||
node tools/fix-files.js
|
||||
```
|
||||
|
||||
### Step 7: TypeScript Validation
|
||||
|
||||
```bash
|
||||
# Full TypeScript compilation check
|
||||
npx tsc --noEmit
|
||||
|
||||
# If errors, show affected files
|
||||
npx tsc --noEmit | head -20
|
||||
```
|
||||
|
||||
Document compilation errors:
|
||||
- Missing properties
|
||||
- Type mismatches
|
||||
- Incompatible signatures
|
||||
|
||||
### Step 8: Run Affected Tests
|
||||
|
||||
```bash
|
||||
# Run tests for affected projects
|
||||
npx nx affected:test --skip-nx-cache --base=HEAD~1
|
||||
|
||||
# Lint affected projects
|
||||
npx nx affected:lint --base=HEAD~1
|
||||
```
|
||||
|
||||
Monitor test failures and categorize:
|
||||
- Mock data mismatches (update fixtures)
|
||||
- Type assertion failures (update test types)
|
||||
- Integration test failures (API contract changes)
|
||||
|
||||
### Step 9: Generate Comprehensive Report
|
||||
|
||||
```
|
||||
API Sync Manager Report
|
||||
=======================
|
||||
|
||||
API: [api-name | all]
|
||||
Sync Date: [timestamp]
|
||||
Generation: ✅ Success / ❌ Failed
|
||||
|
||||
📊 Change Summary
|
||||
-----------------
|
||||
Breaking Changes: XX
|
||||
Warnings: XX
|
||||
Compatible Changes: XX
|
||||
Files Modified: XX
|
||||
|
||||
🔴 Breaking Changes
|
||||
-------------------
|
||||
1. [API Name] - Removed Property: OrderResponse.deliveryDate
|
||||
Impact: Medium (2 files affected)
|
||||
Files:
|
||||
- libs/oms/data-access/src/lib/services/order.service.ts:45
|
||||
- libs/oms/feature/order-detail/src/lib/component.ts:78
|
||||
Fix Strategy: Remove references or use alternativeDate field
|
||||
Estimated Effort: 30 minutes
|
||||
|
||||
2. [API Name] - Type Changed: ProductResponse.price (string → number)
|
||||
Impact: High (1 file + cascading changes)
|
||||
Files:
|
||||
- libs/catalogue/data-access/src/lib/services/product.service.ts:32
|
||||
Fix Strategy: Remove string parsing, update price calculations
|
||||
Estimated Effort: 1 hour
|
||||
|
||||
⚠️ Warnings (Review Required)
|
||||
------------------------------
|
||||
1. [API Name] - Possible Rename: CustomerResponse.customerName → fullName
|
||||
Action: Verify with backend team if intentional
|
||||
Migration: Update references if confirmed rename
|
||||
|
||||
2. [API Name] - New Required Field: CreateOrderRequest.taxId
|
||||
Action: Update order creation flows to provide taxId
|
||||
Impact: 3 order creation forms
|
||||
|
||||
✅ Compatible Changes
|
||||
---------------------
|
||||
1. [API Name] - Added Property: OrderResponse.estimatedDelivery
|
||||
2. [API Name] - New Endpoint: GET /api/v2/orders/bulk
|
||||
3. [API Name] - Added Optional Parameter: includeArchived to GET /orders
|
||||
|
||||
📊 Validation Results
|
||||
---------------------
|
||||
TypeScript Compilation: ✅ Pass / ❌ XX errors
|
||||
Unicode Cleanup: ✅ Complete
|
||||
Tests: XX/XX passing (YY affected)
|
||||
Lint: ✅ Pass / ⚠️ XX warnings
|
||||
|
||||
❌ Test Failures (if any)
|
||||
-------------------------
|
||||
1. order.service.spec.ts - Mock response missing deliveryDate
|
||||
Fix: Update mock fixture
|
||||
2. product.component.spec.ts - Price type assertion failed
|
||||
Fix: Change expect(price).toBe("10.99") → expect(price).toBe(10.99)
|
||||
|
||||
💡 Migration Strategy
|
||||
---------------------
|
||||
Approach: [High/Medium/Low Impact]
|
||||
Estimated Total Effort: [hours]
|
||||
|
||||
Steps:
|
||||
1. [Fix compilation errors in data-access services]
|
||||
2. [Update test mocks and fixtures]
|
||||
3. [Update components using affected properties]
|
||||
4. [Run full test suite]
|
||||
5. [Deploy with backend coordination]
|
||||
|
||||
🎯 Recommendation
|
||||
-----------------
|
||||
[One of the following:]
|
||||
✅ Safe to proceed - Only compatible changes detected
|
||||
⚠️ Proceed with caution - Fix breaking changes before deployment
|
||||
🔴 Coordinate with backend - High impact changes require migration plan
|
||||
🛑 Block regeneration - Critical breaking changes, backend rollback needed
|
||||
|
||||
📋 Next Steps
|
||||
-------------
|
||||
[Specific actions user should take]
|
||||
- [ ] Fix compilation errors in [files]
|
||||
- [ ] Update test mocks in [files]
|
||||
- [ ] Coordinate deployment timing with backend team
|
||||
- [ ] Monitor [specific endpoints] after deployment
|
||||
```
|
||||
|
||||
### Step 10: Cleanup
|
||||
|
||||
```bash
|
||||
# Remove temporary backup
|
||||
rm -rf /tmp/[api-name].backup
|
||||
|
||||
# Or restore if generation needs to be reverted
|
||||
# cp -r /tmp/[api-name].backup generated/swagger/[api-name]
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
**Generation Fails:**
|
||||
- Check OpenAPI spec URLs in package.json scripts
|
||||
- Verify backend API is accessible
|
||||
- Check for malformed OpenAPI specification
|
||||
|
||||
**Unicode Cleanup Fails:**
|
||||
- Run `node tools/fix-files.js` manually
|
||||
- Check for new Unicode patterns not covered by script
|
||||
|
||||
**TypeScript Compilation Errors:**
|
||||
- Review breaking changes section in report
|
||||
- Update affected data-access services first
|
||||
- Fix cascading type errors in consumers
|
||||
|
||||
**Test Failures:**
|
||||
- Update mock data to match new API contracts
|
||||
- Fix type assertions in tests
|
||||
- Update integration test expectations
|
||||
|
||||
**Diff Detection Issues:**
|
||||
- Ensure clean git state before running
|
||||
- Check file permissions on generated/ directory
|
||||
- Verify backup location is writable
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
**Compare Specific Endpoints Only:**
|
||||
|
||||
```bash
|
||||
# Extract and compare specific interface
|
||||
grep -A 20 "interface OrderResponse" /tmp/[api-name].backup/models.ts
|
||||
grep -A 20 "interface OrderResponse" generated/swagger/[api-name]/models.ts
|
||||
```
|
||||
|
||||
**Bulk API Sync with Change Detection:**
|
||||
|
||||
Run pre-generation detection for all APIs before regenerating:
|
||||
|
||||
```bash
|
||||
for api in availability-api cat-search-api checkout-api crm-api eis-api inventory-api isa-api oms-api print-api wws-api; do
|
||||
echo "Checking $api..."
|
||||
# Run detection workflow for each
|
||||
done
|
||||
```
|
||||
|
||||
Generate consolidated report across all APIs before committing.
|
||||
|
||||
**Rollback Procedure:**
|
||||
|
||||
```bash
|
||||
# If sync causes critical issues
|
||||
git checkout generated/swagger/[api-name]
|
||||
git clean -fd generated/swagger/[api-name]
|
||||
|
||||
# Or restore from backup
|
||||
cp -r generated/swagger.backup.[timestamp]/* generated/swagger/
|
||||
```
|
||||
|
||||
## Integration with Git Workflow
|
||||
|
||||
**Recommended Commit Message:**
|
||||
|
||||
```
|
||||
feat(api): sync [api-name] API client [TASK-####]
|
||||
|
||||
Breaking changes:
|
||||
- Removed OrderResponse.deliveryDate (use estimatedDelivery)
|
||||
- Changed ProductResponse.price type (string → number)
|
||||
|
||||
Compatible changes:
|
||||
- Added OrderResponse.estimatedDelivery
|
||||
- New endpoint GET /api/v2/orders/bulk
|
||||
```
|
||||
|
||||
**Branch Strategy:**
|
||||
|
||||
- Low impact: Commit to feature branch directly
|
||||
- Medium impact: Create `sync/[api-name]-[date]` branch
|
||||
- High impact: Create `migration/[api-name]-[version]` branch with migration guide
|
||||
|
||||
## References
|
||||
|
||||
- CLAUDE.md API Integration section
|
||||
- package.json swagger generation scripts
|
||||
- tools/fix-files.js for Unicode cleanup logic
|
||||
- Semantic Versioning: https://semver.org
|
||||
|
||||
@@ -1,171 +1,177 @@
|
||||
---
|
||||
name: arch-docs
|
||||
description: Generate architecture documentation (C4, Arc42, ADRs, PlantUML). Auto-invoke when user mentions "architecture docs", "C4 model", "ADR", "document architecture", "system design", or "create architecture diagram".
|
||||
---
|
||||
|
||||
# Architecture Documentation Skill
|
||||
|
||||
Generate comprehensive architecture documentation using modern frameworks and best practices.
|
||||
|
||||
## When to Use
|
||||
|
||||
- Creating or updating architecture documentation
|
||||
- Generating C4 model diagrams (Context, Container, Component, Code)
|
||||
- Writing Architecture Decision Records (ADRs)
|
||||
- Documenting system design and component relationships
|
||||
- Creating PlantUML or Mermaid diagrams
|
||||
|
||||
## Available Frameworks
|
||||
|
||||
### C4 Model
|
||||
Best for: Visualizing software architecture at different abstraction levels
|
||||
|
||||
Levels:
|
||||
1. **Context** - System landscape and external actors
|
||||
2. **Container** - High-level technology choices (apps, databases, etc.)
|
||||
3. **Component** - Internal structure of containers
|
||||
4. **Code** - Class/module level detail (optional)
|
||||
|
||||
See: `@references/c4-model.md` for patterns and examples
|
||||
|
||||
### Arc42 Template
|
||||
Best for: Comprehensive architecture documentation
|
||||
|
||||
Sections:
|
||||
1. Introduction and Goals
|
||||
2. Constraints
|
||||
3. Context and Scope
|
||||
4. Solution Strategy
|
||||
5. Building Block View
|
||||
6. Runtime View
|
||||
7. Deployment View
|
||||
8. Cross-cutting Concepts
|
||||
9. Architecture Decisions
|
||||
10. Quality Requirements
|
||||
11. Risks and Technical Debt
|
||||
12. Glossary
|
||||
|
||||
See: `@references/arc42.md` for template structure
|
||||
|
||||
### Architecture Decision Records (ADRs)
|
||||
Best for: Documenting individual architectural decisions
|
||||
|
||||
See: `@references/adr-template.md` for format and examples
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Discovery Phase
|
||||
```bash
|
||||
# Find existing architecture files
|
||||
find . -name "*architecture*" -o -name "*.puml" -o -name "*.mmd"
|
||||
|
||||
# Identify service boundaries
|
||||
cat nx.json docker-compose.yml
|
||||
|
||||
# Check for existing ADRs
|
||||
ls -la docs/adr/ docs/decisions/
|
||||
```
|
||||
|
||||
### 2. Analysis Phase
|
||||
- Analyze codebase structure (`libs/`, `apps/`)
|
||||
- Identify dependencies from `tsconfig.base.json` paths
|
||||
- Review service boundaries from `project.json` tags
|
||||
- Map data flow from API definitions
|
||||
|
||||
### 3. Documentation Phase
|
||||
Based on the request, create appropriate documentation:
|
||||
|
||||
**For C4 diagrams:**
|
||||
```
|
||||
docs/architecture/
|
||||
├── c4-context.puml
|
||||
├── c4-container.puml
|
||||
└── c4-component-[name].puml
|
||||
```
|
||||
|
||||
**For ADRs:**
|
||||
```
|
||||
docs/adr/
|
||||
├── 0001-record-architecture-decisions.md
|
||||
├── 0002-[decision-title].md
|
||||
└── template.md
|
||||
```
|
||||
|
||||
**For Arc42:**
|
||||
```
|
||||
docs/architecture/
|
||||
└── arc42/
|
||||
├── 01-introduction.md
|
||||
├── 02-constraints.md
|
||||
└── ...
|
||||
```
|
||||
|
||||
## ISA-Frontend Specific Context
|
||||
|
||||
### Monorepo Structure
|
||||
- **apps/**: Angular applications
|
||||
- **libs/**: Shared libraries organized by domain
|
||||
- `libs/[domain]/feature/` - Feature modules
|
||||
- `libs/[domain]/data-access/` - State management
|
||||
- `libs/[domain]/ui/` - Presentational components
|
||||
- `libs/[domain]/util/` - Utilities
|
||||
|
||||
### Key Architectural Patterns
|
||||
- **Nx Monorepo** with strict module boundaries
|
||||
- **NgRx Signal Store** for state management
|
||||
- **Standalone Components** (Angular 20+)
|
||||
- **Domain-Driven Design** library organization
|
||||
|
||||
### Documentation Locations
|
||||
- ADRs: `docs/adr/`
|
||||
- Architecture diagrams: `docs/architecture/`
|
||||
- API documentation: Generated from Swagger/OpenAPI
|
||||
|
||||
## Output Standards
|
||||
|
||||
### PlantUML Format
|
||||
```plantuml
|
||||
@startuml C4_Context
|
||||
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Context.puml
|
||||
|
||||
Person(user, "User", "System user")
|
||||
System(system, "ISA System", "Main application")
|
||||
System_Ext(external, "External API", "Third-party service")
|
||||
|
||||
Rel(user, system, "Uses")
|
||||
Rel(system, external, "Calls")
|
||||
@enduml
|
||||
```
|
||||
|
||||
### Mermaid Format
|
||||
```mermaid
|
||||
graph TD
|
||||
A[User] --> B[ISA App]
|
||||
B --> C[API Gateway]
|
||||
C --> D[Backend Services]
|
||||
```
|
||||
|
||||
### ADR Format
|
||||
```markdown
|
||||
# ADR-XXXX: [Title]
|
||||
|
||||
## Status
|
||||
[Proposed | Accepted | Deprecated | Superseded]
|
||||
|
||||
## Context
|
||||
[What is the issue?]
|
||||
|
||||
## Decision
|
||||
[What was decided?]
|
||||
|
||||
## Consequences
|
||||
[What are the results?]
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Start with Context** - Always begin with C4 Level 1 (System Context)
|
||||
2. **Use Consistent Notation** - Stick to one diagramming tool/format
|
||||
3. **Keep ADRs Atomic** - One decision per ADR
|
||||
4. **Version Control** - Commit documentation with code changes
|
||||
5. **Review Regularly** - Architecture docs decay; schedule reviews
|
||||
---
|
||||
name: arch-docs
|
||||
description: Generate architecture documentation (C4, Arc42, ADRs, PlantUML). Auto-invoke when user mentions "architecture docs", "C4 model", "ADR", "document architecture", "system design", or "create architecture diagram".
|
||||
---
|
||||
|
||||
# Architecture Documentation
|
||||
|
||||
Generate comprehensive architecture documentation using modern frameworks and best practices.
|
||||
|
||||
## Available Frameworks
|
||||
|
||||
### C4 Model
|
||||
Visualize software architecture at different abstraction levels.
|
||||
|
||||
**Levels:**
|
||||
1. **Context** - System landscape and external actors
|
||||
2. **Container** - High-level technology choices (apps, databases, etc.)
|
||||
3. **Component** - Internal structure of containers
|
||||
4. **Code** - Class/module level detail (optional)
|
||||
|
||||
**When to use:** Visualizing system architecture, creating diagrams for stakeholders at different technical levels.
|
||||
|
||||
See `references/c4-model.md` for detailed patterns and examples.
|
||||
|
||||
### Arc42 Template
|
||||
Comprehensive architecture documentation covering all aspects.
|
||||
|
||||
**Sections:**
|
||||
1. Introduction and Goals
|
||||
2. Constraints
|
||||
3. Context and Scope
|
||||
4. Solution Strategy
|
||||
5. Building Block View
|
||||
6. Runtime View
|
||||
7. Deployment View
|
||||
8. Cross-cutting Concepts
|
||||
9. Architecture Decisions
|
||||
10. Quality Requirements
|
||||
11. Risks and Technical Debt
|
||||
12. Glossary
|
||||
|
||||
**When to use:** Creating complete architecture documentation, documenting system-wide concerns, establishing quality goals.
|
||||
|
||||
See `references/arc42.md` for complete template structure.
|
||||
|
||||
### Architecture Decision Records (ADRs)
|
||||
Document individual architectural decisions with context and consequences.
|
||||
|
||||
**When to use:** Recording significant decisions, documenting trade-offs, tracking architectural evolution.
|
||||
|
||||
See `references/adr-template.md` for format and examples.
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Discovery Phase
|
||||
Understand the system structure:
|
||||
|
||||
```bash
|
||||
# Find existing architecture files
|
||||
find . -name "*architecture*" -o -name "*.puml" -o -name "*.mmd"
|
||||
|
||||
# Identify service boundaries
|
||||
cat nx.json docker-compose.yml
|
||||
|
||||
# Check for existing ADRs
|
||||
ls -la docs/adr/ docs/decisions/
|
||||
```
|
||||
|
||||
### 2. Analysis Phase
|
||||
- Analyze codebase structure (`libs/`, `apps/`)
|
||||
- Identify dependencies from `tsconfig.base.json` paths
|
||||
- Review service boundaries from `project.json` tags
|
||||
- Map data flow from API definitions
|
||||
|
||||
### 3. Documentation Phase
|
||||
Choose appropriate output format based on request:
|
||||
|
||||
**For C4 diagrams:**
|
||||
```
|
||||
docs/architecture/
|
||||
├── c4-context.puml
|
||||
├── c4-container.puml
|
||||
└── c4-component-[name].puml
|
||||
```
|
||||
|
||||
**For ADRs:**
|
||||
```
|
||||
docs/adr/
|
||||
├── 0001-record-architecture-decisions.md
|
||||
├── 0002-[decision-title].md
|
||||
└── template.md
|
||||
```
|
||||
|
||||
**For Arc42:**
|
||||
```
|
||||
docs/architecture/
|
||||
└── arc42/
|
||||
├── 01-introduction.md
|
||||
├── 02-constraints.md
|
||||
└── ...
|
||||
```
|
||||
|
||||
## ISA-Frontend Context
|
||||
|
||||
### Monorepo Structure
|
||||
- **apps/**: Angular applications
|
||||
- **libs/**: Shared libraries organized by domain
|
||||
- `libs/[domain]/feature/` - Feature modules
|
||||
- `libs/[domain]/data-access/` - State management
|
||||
- `libs/[domain]/ui/` - Presentational components
|
||||
- `libs/[domain]/util/` - Utilities
|
||||
|
||||
### Key Architectural Patterns
|
||||
- **Nx Monorepo** with strict module boundaries
|
||||
- **NgRx Signal Store** for state management
|
||||
- **Standalone Components** (Angular 20+)
|
||||
- **Domain-Driven Design** library organization
|
||||
|
||||
### Documentation Locations
|
||||
- ADRs: `docs/adr/`
|
||||
- Architecture diagrams: `docs/architecture/`
|
||||
- API documentation: Generated from Swagger/OpenAPI
|
||||
|
||||
## Output Standards
|
||||
|
||||
### PlantUML Format
|
||||
```plantuml
|
||||
@startuml C4_Context
|
||||
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Context.puml
|
||||
|
||||
Person(user, "User", "System user")
|
||||
System(system, "ISA System", "Main application")
|
||||
System_Ext(external, "External API", "Third-party service")
|
||||
|
||||
Rel(user, system, "Uses")
|
||||
Rel(system, external, "Calls")
|
||||
@enduml
|
||||
```
|
||||
|
||||
### Mermaid Format
|
||||
```mermaid
|
||||
graph TD
|
||||
A[User] --> B[ISA App]
|
||||
B --> C[API Gateway]
|
||||
C --> D[Backend Services]
|
||||
```
|
||||
|
||||
### ADR Format
|
||||
```markdown
|
||||
# ADR-XXXX: [Title]
|
||||
|
||||
## Status
|
||||
[Proposed | Accepted | Deprecated | Superseded]
|
||||
|
||||
## Context
|
||||
[What is the issue?]
|
||||
|
||||
## Decision
|
||||
[What was decided?]
|
||||
|
||||
## Consequences
|
||||
[What are the results?]
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Start with Context** - Always begin with C4 Level 1 (System Context)
|
||||
2. **Use Consistent Notation** - Stick to one diagramming tool/format
|
||||
3. **Keep ADRs Atomic** - One decision per ADR
|
||||
4. **Version Control** - Commit documentation with code changes
|
||||
5. **Review Regularly** - Architecture docs decay; schedule reviews
|
||||
|
||||
## References
|
||||
|
||||
- `references/c4-model.md` - C4 model patterns, templates, and ISA-Frontend domain structure
|
||||
- `references/arc42.md` - Complete Arc42 template with all 12 sections
|
||||
- `references/adr-template.md` - ADR format with examples and naming conventions
|
||||
|
||||
@@ -9,16 +9,6 @@ description: This skill should be used when validating architecture compliance,
|
||||
|
||||
Validate and enforce architectural boundaries in the ISA-Frontend monorepo. Detects import boundary violations, circular dependencies, layer violations, and cross-domain dependencies. Provides automated fix strategies using graph analysis, dependency injection, interface extraction, and shared code refactoring.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Invoke when user wants to:
|
||||
- Validate import boundaries or architectural rules
|
||||
- Detect circular dependencies or dependency cycles
|
||||
- Find layer violations (Feature→Feature, Data Access→Feature)
|
||||
- Check cross-domain dependencies
|
||||
- Resolve build failures with circular import warnings
|
||||
- Mentioned "check architecture", "validate boundaries", "circular dependencies", or "dependency cycles"
|
||||
|
||||
## Architectural Rules
|
||||
|
||||
**Allowed Dependencies:**
|
||||
|
||||
@@ -9,17 +9,6 @@ description: This skill should be used when writing or reviewing CSS animations
|
||||
|
||||
Implement native CSS @keyframes animations for Angular applications, replacing @angular/animations with GPU-accelerated, zero-bundle-size alternatives. This skill provides comprehensive guidance on creating performant entrance/exit animations, staggered effects, and proper timing configurations.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Apply this skill when:
|
||||
- **Writing Angular components** with entrance/exit animations
|
||||
- **Converting @angular/animations** to native CSS @keyframes
|
||||
- **Implementing animate.enter/animate.leave** in Angular 20+ templates
|
||||
- **Creating staggered animations** for lists or collections
|
||||
- **Debugging animation issues** (snap-back, wrong starting positions, choppy playback)
|
||||
- **Optimizing animation performance** for GPU acceleration
|
||||
- **Reviewing animation code** for accessibility and best practices
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Basic Animation Setup
|
||||
@@ -311,41 +300,6 @@ element.addEventListener('animationend', (e) => {
|
||||
});
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
### references/keyframes-guide.md
|
||||
|
||||
Comprehensive deep-dive covering:
|
||||
- Complete @keyframes syntax reference
|
||||
- Detailed timing functions and cubic-bezier curves
|
||||
- Advanced techniques (direction, play-state, @starting-style)
|
||||
- Performance optimization strategies
|
||||
- Extensive common patterns library
|
||||
- Debugging techniques and troubleshooting
|
||||
|
||||
**When to reference:** Complex animation requirements, custom easing curves, advanced techniques, performance optimization, or learning comprehensive details.
|
||||
|
||||
### assets/animations.css
|
||||
|
||||
Ready-to-use CSS file with common animation patterns:
|
||||
- Fade animations (in/out)
|
||||
- Slide animations (up/down/left/right)
|
||||
- Scale animations (in/out)
|
||||
- Utility animations (spin, shimmer, shake, breathe, attention-pulse)
|
||||
- Toast/notification animations
|
||||
- Accessibility (@media prefers-reduced-motion)
|
||||
|
||||
**Usage:** Copy this file to project and import in component styles or global styles:
|
||||
|
||||
```css
|
||||
@import 'path/to/animations.css';
|
||||
```
|
||||
|
||||
Then use classes directly:
|
||||
```html
|
||||
<div animate.enter="fade-in" animate.leave="slide-out-down">
|
||||
```
|
||||
|
||||
## Migration from @angular/animations
|
||||
|
||||
### Before (Angular Animations)
|
||||
@@ -390,3 +344,38 @@ import { trigger, state, style, transition, animate } from '@angular/animations'
|
||||
- GPU hardware acceleration
|
||||
- Standard CSS (transferable skills)
|
||||
- Better performance
|
||||
|
||||
## Resources
|
||||
|
||||
### references/keyframes-guide.md
|
||||
|
||||
Comprehensive deep-dive covering:
|
||||
- Complete @keyframes syntax reference
|
||||
- Detailed timing functions and cubic-bezier curves
|
||||
- Advanced techniques (direction, play-state, @starting-style)
|
||||
- Performance optimization strategies
|
||||
- Extensive common patterns library
|
||||
- Debugging techniques and troubleshooting
|
||||
|
||||
**When to reference:** Complex animation requirements, custom easing curves, advanced techniques, performance optimization, or learning comprehensive details.
|
||||
|
||||
### assets/animations.css
|
||||
|
||||
Ready-to-use CSS file with common animation patterns:
|
||||
- Fade animations (in/out)
|
||||
- Slide animations (up/down/left/right)
|
||||
- Scale animations (in/out)
|
||||
- Utility animations (spin, shimmer, shake, breathe, attention-pulse)
|
||||
- Toast/notification animations
|
||||
- Accessibility (@media prefers-reduced-motion)
|
||||
|
||||
**Usage:** Copy this file to project and import in component styles or global styles:
|
||||
|
||||
```css
|
||||
@import 'path/to/animations.css';
|
||||
```
|
||||
|
||||
Then use classes directly:
|
||||
```html
|
||||
<div animate.enter="fade-in" animate.leave="slide-out-down">
|
||||
```
|
||||
|
||||
@@ -1,352 +1,352 @@
|
||||
---
|
||||
name: git-workflow
|
||||
description: This skill should be used when creating branches, writing commits, or creating pull requests. Enforces ISA-Frontend Git conventions including feature/task-id-name branch format, conventional commits without co-author tags, and PRs targeting develop branch.
|
||||
---
|
||||
|
||||
# Git Workflow Skill
|
||||
|
||||
Enforces Git workflow conventions specific to the ISA-Frontend project.
|
||||
|
||||
## When to Use
|
||||
|
||||
- Creating new branches for features or bugfixes
|
||||
- Writing commit messages
|
||||
- Creating pull requests
|
||||
- Any Git operations requiring adherence to project conventions
|
||||
|
||||
## Core Principles
|
||||
|
||||
### 1. Default Branch is `develop` (NOT `main`)
|
||||
|
||||
- **All PRs target**: `develop` branch
|
||||
- **Feature branches from**: `develop`
|
||||
- **Never push directly to**: `develop` or `main`
|
||||
|
||||
### 2. Branch Naming Convention
|
||||
|
||||
**Format**: `<type>/{task-id}-{short-description}`
|
||||
|
||||
**Types**:
|
||||
- `feature/` - New features or enhancements
|
||||
- `bugfix/` - Bug fixes
|
||||
- `hotfix/` - Emergency production fixes
|
||||
|
||||
**Rules**:
|
||||
- Use English kebab-case for descriptions
|
||||
- Start with task/issue ID (e.g., `5391`)
|
||||
- Keep description concise - shorten if too long
|
||||
- Use hyphens to separate words
|
||||
|
||||
**Examples**:
|
||||
```bash
|
||||
# Good
|
||||
feature/5391-praemie-checkout-action-card-delivery-order
|
||||
bugfix/6123-fix-login-redirect-loop
|
||||
hotfix/7890-critical-payment-error
|
||||
|
||||
# Bad
|
||||
feature/praemie-checkout # Missing task ID
|
||||
feature/5391_praemie # Using underscores
|
||||
feature-5391-very-long-description-that-goes-on-forever # Too long
|
||||
```
|
||||
|
||||
### 3. Conventional Commits (WITHOUT Co-Author Tags)
|
||||
|
||||
**Format**: `<type>(<scope>): <description>`
|
||||
|
||||
**Types**:
|
||||
- `feat`: New feature
|
||||
- `fix`: Bug fix
|
||||
- `docs`: Documentation only
|
||||
- `style`: Code style (formatting, missing semicolons)
|
||||
- `refactor`: Code restructuring without feature changes
|
||||
- `perf`: Performance improvements
|
||||
- `test`: Adding or updating tests
|
||||
- `build`: Build system or dependencies
|
||||
- `ci`: CI configuration
|
||||
- `chore`: Maintenance tasks
|
||||
|
||||
**Rules**:
|
||||
- ❌ **NO** "Generated with Claude Code" tags
|
||||
- ❌ **NO** "Co-Authored-By: Claude" tags
|
||||
- ✅ Keep first line under 72 characters
|
||||
- ✅ Use imperative mood ("add" not "added")
|
||||
- ✅ Body optional but recommended for complex changes
|
||||
|
||||
**Examples**:
|
||||
```bash
|
||||
# Good
|
||||
feat(checkout): add bonus card selection for delivery orders
|
||||
|
||||
fix(crm): resolve customer search filter reset issue
|
||||
|
||||
refactor(oms): extract return validation logic into service
|
||||
|
||||
# Bad
|
||||
feat(checkout): add bonus card selection
|
||||
|
||||
Generated with Claude Code
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>
|
||||
|
||||
# Also bad
|
||||
Added new feature # Wrong tense
|
||||
Fix bug # Missing scope
|
||||
```
|
||||
|
||||
### 4. Pull Request Creation
|
||||
|
||||
**Target Branch**: Always `develop`
|
||||
|
||||
**PR Title Format**: Same as conventional commit
|
||||
```
|
||||
feat(domain): concise description of changes
|
||||
```
|
||||
|
||||
**PR Body Structure**:
|
||||
```markdown
|
||||
## Summary
|
||||
- Brief bullet points of changes
|
||||
|
||||
## Related Tasks
|
||||
- Closes #{task-id}
|
||||
- Refs #{related-task}
|
||||
|
||||
## Test Plan
|
||||
- [ ] Unit tests added/updated
|
||||
- [ ] E2E attributes added
|
||||
- [ ] Manual testing completed
|
||||
|
||||
## Breaking Changes
|
||||
None / List breaking changes
|
||||
|
||||
## Screenshots (if UI changes)
|
||||
[Add screenshots]
|
||||
```
|
||||
|
||||
## Common Workflows
|
||||
|
||||
### Creating a Feature Branch
|
||||
|
||||
```bash
|
||||
# 1. Update develop
|
||||
git checkout develop
|
||||
git pull origin develop
|
||||
|
||||
# 2. Create feature branch
|
||||
git checkout -b feature/5391-praemie-checkout-action-card
|
||||
|
||||
# 3. Work and commit
|
||||
git add .
|
||||
git commit -m "feat(checkout): add primary bonus card selection logic"
|
||||
|
||||
# 4. Push to remote
|
||||
git push -u origin feature/5391-praemie-checkout-action-card
|
||||
|
||||
# 5. Create PR targeting develop (use gh CLI or web UI)
|
||||
```
|
||||
|
||||
### Creating a Bugfix Branch
|
||||
|
||||
```bash
|
||||
# From develop
|
||||
git checkout develop
|
||||
git pull origin develop
|
||||
git checkout -b bugfix/6123-login-redirect-loop
|
||||
|
||||
# Commit
|
||||
git commit -m "fix(auth): resolve infinite redirect on logout"
|
||||
```
|
||||
|
||||
### Creating a Hotfix Branch
|
||||
|
||||
```bash
|
||||
# From main (production)
|
||||
git checkout main
|
||||
git pull origin main
|
||||
git checkout -b hotfix/7890-payment-processing-error
|
||||
|
||||
# Commit
|
||||
git commit -m "fix(checkout): critical payment API timeout handling"
|
||||
|
||||
# Merge to both main and develop
|
||||
```
|
||||
|
||||
## Commit Message Guidelines
|
||||
|
||||
### Good Commit Messages
|
||||
|
||||
```bash
|
||||
feat(crm): add customer loyalty tier calculation
|
||||
|
||||
Implements three-tier loyalty system based on annual spend.
|
||||
Includes migration for existing customer data.
|
||||
|
||||
Refs #5234
|
||||
|
||||
---
|
||||
|
||||
fix(oms): prevent duplicate return submissions
|
||||
|
||||
Adds debouncing to return form submission and validates
|
||||
against existing returns in the last 60 seconds.
|
||||
|
||||
Closes #5891
|
||||
|
||||
---
|
||||
|
||||
refactor(catalogue): extract product search into dedicated service
|
||||
|
||||
Moves search logic from component to ProductSearchService
|
||||
for better testability and reusability.
|
||||
|
||||
---
|
||||
|
||||
perf(remission): optimize remission list query with pagination
|
||||
|
||||
Reduces initial load time from 3s to 800ms by implementing
|
||||
cursor-based pagination.
|
||||
|
||||
Closes #6234
|
||||
```
|
||||
|
||||
### Bad Commit Messages
|
||||
|
||||
```bash
|
||||
# Too vague
|
||||
fix: bug fixes
|
||||
|
||||
# Missing scope
|
||||
feat: new feature
|
||||
|
||||
# Wrong tense
|
||||
fixed the login issue
|
||||
|
||||
# Including banned tags
|
||||
feat(checkout): add feature
|
||||
|
||||
Generated with Claude Code
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>
|
||||
```
|
||||
|
||||
## Git Configuration Checks
|
||||
|
||||
### Verify Git Setup
|
||||
|
||||
```bash
|
||||
# Check current branch
|
||||
git branch --show-current
|
||||
|
||||
# Verify remote
|
||||
git remote -v # Should show origin pointing to ISA-Frontend
|
||||
|
||||
# Check for uncommitted changes
|
||||
git status
|
||||
```
|
||||
|
||||
## Common Mistakes to Avoid
|
||||
|
||||
```bash
|
||||
# ❌ Creating PR against main
|
||||
gh pr create --base main # WRONG
|
||||
|
||||
# ✅ Always target develop
|
||||
gh pr create --base develop # CORRECT
|
||||
|
||||
# ❌ Using underscores in branch names
|
||||
git checkout -b feature/5391_my_feature # WRONG
|
||||
|
||||
# ✅ Use hyphens
|
||||
git checkout -b feature/5391-my-feature # CORRECT
|
||||
|
||||
# ❌ Adding co-author tags
|
||||
git commit -m "feat: something
|
||||
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>" # WRONG
|
||||
|
||||
# ✅ Clean commit message
|
||||
git commit -m "feat(scope): something" # CORRECT
|
||||
|
||||
# ❌ Forgetting task ID in branch name
|
||||
git checkout -b feature/new-checkout-flow # WRONG
|
||||
|
||||
# ✅ Include task ID
|
||||
git checkout -b feature/5391-new-checkout-flow # CORRECT
|
||||
```
|
||||
|
||||
## Integration with Claude Code
|
||||
|
||||
When Claude Code creates commits or PRs:
|
||||
|
||||
### Commit Creation
|
||||
```bash
|
||||
# Claude uses conventional commits WITHOUT attribution
|
||||
git commit -m "feat(checkout): implement bonus card selection
|
||||
|
||||
Adds logic for selecting primary bonus card during checkout
|
||||
for delivery orders. Includes validation and error handling.
|
||||
|
||||
Refs #5391"
|
||||
```
|
||||
|
||||
### PR Creation
|
||||
```bash
|
||||
# Target develop by default
|
||||
gh pr create --base develop \
|
||||
--title "feat(checkout): implement bonus card selection" \
|
||||
--body "## Summary
|
||||
- Add primary bonus card selection logic
|
||||
- Implement validation for delivery orders
|
||||
- Add error handling for API failures
|
||||
|
||||
## Related Tasks
|
||||
- Closes #5391
|
||||
|
||||
## Test Plan
|
||||
- [x] Unit tests added
|
||||
- [x] E2E attributes added
|
||||
- [x] Manual testing completed"
|
||||
```
|
||||
|
||||
## Branch Cleanup
|
||||
|
||||
### After PR Merge
|
||||
```bash
|
||||
# Update develop
|
||||
git checkout develop
|
||||
git pull origin develop
|
||||
|
||||
# Delete local feature branch
|
||||
git branch -d feature/5391-praemie-checkout
|
||||
|
||||
# Delete remote branch (usually done by PR merge)
|
||||
git push origin --delete feature/5391-praemie-checkout
|
||||
```
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```bash
|
||||
# Branch naming
|
||||
feature/{task-id}-{description}
|
||||
bugfix/{task-id}-{description}
|
||||
hotfix/{task-id}-{description}
|
||||
|
||||
# Commit format
|
||||
<type>(<scope>): <description>
|
||||
|
||||
# Common types
|
||||
feat, fix, docs, style, refactor, perf, test, build, ci, chore
|
||||
|
||||
# PR target
|
||||
Always: develop (NOT main)
|
||||
|
||||
# Banned in commits
|
||||
- "Generated with Claude Code"
|
||||
- "Co-Authored-By: Claude"
|
||||
- Any AI attribution
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [Conventional Commits](https://www.conventionalcommits.org/)
|
||||
- Project PR template: `.github/pull_request_template.md`
|
||||
- Code review standards: `.github/review-instructions.md`
|
||||
---
|
||||
name: git-workflow
|
||||
description: This skill should be used when creating branches, writing commits, or creating pull requests. Enforces ISA-Frontend Git conventions including feature/task-id-name branch format, conventional commits without co-author tags, and PRs targeting develop branch.
|
||||
---
|
||||
|
||||
# Git Workflow Skill
|
||||
|
||||
Enforces Git workflow conventions specific to the ISA-Frontend project.
|
||||
|
||||
## When to Use
|
||||
|
||||
- Creating new branches for features or bugfixes
|
||||
- Writing commit messages
|
||||
- Creating pull requests
|
||||
- Any Git operations requiring adherence to project conventions
|
||||
|
||||
## Core Principles
|
||||
|
||||
### 1. Default Branch is `develop` (NOT `main`)
|
||||
|
||||
- **All PRs target**: `develop` branch
|
||||
- **Feature branches from**: `develop`
|
||||
- **Never push directly to**: `develop` or `main`
|
||||
|
||||
### 2. Branch Naming Convention
|
||||
|
||||
**Format**: `<type>/{task-id}-{short-description}`
|
||||
|
||||
**Types**:
|
||||
- `feature/` - New features or enhancements
|
||||
- `bugfix/` - Bug fixes
|
||||
- `hotfix/` - Emergency production fixes
|
||||
|
||||
**Rules**:
|
||||
- Use English kebab-case for descriptions
|
||||
- Start with task/issue ID (e.g., `5391`)
|
||||
- Keep description concise - shorten if too long
|
||||
- Use hyphens to separate words
|
||||
|
||||
**Examples**:
|
||||
```bash
|
||||
# Good
|
||||
feature/5391-praemie-checkout-action-card-delivery-order
|
||||
bugfix/6123-fix-login-redirect-loop
|
||||
hotfix/7890-critical-payment-error
|
||||
|
||||
# Bad
|
||||
feature/praemie-checkout # Missing task ID
|
||||
feature/5391_praemie # Using underscores
|
||||
feature-5391-very-long-description-that-goes-on-forever # Too long
|
||||
```
|
||||
|
||||
### 3. Conventional Commits (WITHOUT Co-Author Tags)
|
||||
|
||||
**Format**: `<type>(<scope>): <description>`
|
||||
|
||||
**Types**:
|
||||
- `feat`: New feature
|
||||
- `fix`: Bug fix
|
||||
- `docs`: Documentation only
|
||||
- `style`: Code style (formatting, missing semicolons)
|
||||
- `refactor`: Code restructuring without feature changes
|
||||
- `perf`: Performance improvements
|
||||
- `test`: Adding or updating tests
|
||||
- `build`: Build system or dependencies
|
||||
- `ci`: CI configuration
|
||||
- `chore`: Maintenance tasks
|
||||
|
||||
**Rules**:
|
||||
- ❌ **NO** "Generated with Claude Code" tags
|
||||
- ❌ **NO** "Co-Authored-By: Claude" tags
|
||||
- ✅ Keep first line under 72 characters
|
||||
- ✅ Use imperative mood ("add" not "added")
|
||||
- ✅ Body optional but recommended for complex changes
|
||||
|
||||
**Examples**:
|
||||
```bash
|
||||
# Good
|
||||
feat(checkout): add bonus card selection for delivery orders
|
||||
|
||||
fix(crm): resolve customer search filter reset issue
|
||||
|
||||
refactor(oms): extract return validation logic into service
|
||||
|
||||
# Bad
|
||||
feat(checkout): add bonus card selection
|
||||
|
||||
Generated with Claude Code
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>
|
||||
|
||||
# Also bad
|
||||
Added new feature # Wrong tense
|
||||
Fix bug # Missing scope
|
||||
```
|
||||
|
||||
### 4. Pull Request Creation
|
||||
|
||||
**Target Branch**: Always `develop`
|
||||
|
||||
**PR Title Format**: Same as conventional commit
|
||||
```
|
||||
feat(domain): concise description of changes
|
||||
```
|
||||
|
||||
**PR Body Structure**:
|
||||
```markdown
|
||||
## Summary
|
||||
- Brief bullet points of changes
|
||||
|
||||
## Related Tasks
|
||||
- Closes #{task-id}
|
||||
- Refs #{related-task}
|
||||
|
||||
## Test Plan
|
||||
- [ ] Unit tests added/updated
|
||||
- [ ] E2E attributes added
|
||||
- [ ] Manual testing completed
|
||||
|
||||
## Breaking Changes
|
||||
None / List breaking changes
|
||||
|
||||
## Screenshots (if UI changes)
|
||||
[Add screenshots]
|
||||
```
|
||||
|
||||
## Common Workflows
|
||||
|
||||
### Creating a Feature Branch
|
||||
|
||||
```bash
|
||||
# 1. Update develop
|
||||
git checkout develop
|
||||
git pull origin develop
|
||||
|
||||
# 2. Create feature branch
|
||||
git checkout -b feature/5391-praemie-checkout-action-card
|
||||
|
||||
# 3. Work and commit
|
||||
git add .
|
||||
git commit -m "feat(checkout): add primary bonus card selection logic"
|
||||
|
||||
# 4. Push to remote
|
||||
git push -u origin feature/5391-praemie-checkout-action-card
|
||||
|
||||
# 5. Create PR targeting develop (use gh CLI or web UI)
|
||||
```
|
||||
|
||||
### Creating a Bugfix Branch
|
||||
|
||||
```bash
|
||||
# From develop
|
||||
git checkout develop
|
||||
git pull origin develop
|
||||
git checkout -b bugfix/6123-login-redirect-loop
|
||||
|
||||
# Commit
|
||||
git commit -m "fix(auth): resolve infinite redirect on logout"
|
||||
```
|
||||
|
||||
### Creating a Hotfix Branch
|
||||
|
||||
```bash
|
||||
# From main (production)
|
||||
git checkout main
|
||||
git pull origin main
|
||||
git checkout -b hotfix/7890-payment-processing-error
|
||||
|
||||
# Commit
|
||||
git commit -m "fix(checkout): critical payment API timeout handling"
|
||||
|
||||
# Merge to both main and develop
|
||||
```
|
||||
|
||||
## Commit Message Guidelines
|
||||
|
||||
### Good Commit Messages
|
||||
|
||||
```bash
|
||||
feat(crm): add customer loyalty tier calculation
|
||||
|
||||
Implements three-tier loyalty system based on annual spend.
|
||||
Includes migration for existing customer data.
|
||||
|
||||
Refs #5234
|
||||
|
||||
---
|
||||
|
||||
fix(oms): prevent duplicate return submissions
|
||||
|
||||
Adds debouncing to return form submission and validates
|
||||
against existing returns in the last 60 seconds.
|
||||
|
||||
Closes #5891
|
||||
|
||||
---
|
||||
|
||||
refactor(catalogue): extract product search into dedicated service
|
||||
|
||||
Moves search logic from component to ProductSearchService
|
||||
for better testability and reusability.
|
||||
|
||||
---
|
||||
|
||||
perf(remission): optimize remission list query with pagination
|
||||
|
||||
Reduces initial load time from 3s to 800ms by implementing
|
||||
cursor-based pagination.
|
||||
|
||||
Closes #6234
|
||||
```
|
||||
|
||||
### Bad Commit Messages
|
||||
|
||||
```bash
|
||||
# Too vague
|
||||
fix: bug fixes
|
||||
|
||||
# Missing scope
|
||||
feat: new feature
|
||||
|
||||
# Wrong tense
|
||||
fixed the login issue
|
||||
|
||||
# Including banned tags
|
||||
feat(checkout): add feature
|
||||
|
||||
Generated with Claude Code
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>
|
||||
```
|
||||
|
||||
## Git Configuration Checks
|
||||
|
||||
### Verify Git Setup
|
||||
|
||||
```bash
|
||||
# Check current branch
|
||||
git branch --show-current
|
||||
|
||||
# Verify remote
|
||||
git remote -v # Should show origin pointing to ISA-Frontend
|
||||
|
||||
# Check for uncommitted changes
|
||||
git status
|
||||
```
|
||||
|
||||
## Common Mistakes to Avoid
|
||||
|
||||
```bash
|
||||
# ❌ Creating PR against main
|
||||
gh pr create --base main # WRONG
|
||||
|
||||
# ✅ Always target develop
|
||||
gh pr create --base develop # CORRECT
|
||||
|
||||
# ❌ Using underscores in branch names
|
||||
git checkout -b feature/5391_my_feature # WRONG
|
||||
|
||||
# ✅ Use hyphens
|
||||
git checkout -b feature/5391-my-feature # CORRECT
|
||||
|
||||
# ❌ Adding co-author tags
|
||||
git commit -m "feat: something
|
||||
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>" # WRONG
|
||||
|
||||
# ✅ Clean commit message
|
||||
git commit -m "feat(scope): something" # CORRECT
|
||||
|
||||
# ❌ Forgetting task ID in branch name
|
||||
git checkout -b feature/new-checkout-flow # WRONG
|
||||
|
||||
# ✅ Include task ID
|
||||
git checkout -b feature/5391-new-checkout-flow # CORRECT
|
||||
```
|
||||
|
||||
## Integration with Claude Code
|
||||
|
||||
When Claude Code creates commits or PRs:
|
||||
|
||||
### Commit Creation
|
||||
```bash
|
||||
# Claude uses conventional commits WITHOUT attribution
|
||||
git commit -m "feat(checkout): implement bonus card selection
|
||||
|
||||
Adds logic for selecting primary bonus card during checkout
|
||||
for delivery orders. Includes validation and error handling.
|
||||
|
||||
Refs #5391"
|
||||
```
|
||||
|
||||
### PR Creation
|
||||
```bash
|
||||
# Target develop by default
|
||||
gh pr create --base develop \
|
||||
--title "feat(checkout): implement bonus card selection" \
|
||||
--body "## Summary
|
||||
- Add primary bonus card selection logic
|
||||
- Implement validation for delivery orders
|
||||
- Add error handling for API failures
|
||||
|
||||
## Related Tasks
|
||||
- Closes #5391
|
||||
|
||||
## Test Plan
|
||||
- [x] Unit tests added
|
||||
- [x] E2E attributes added
|
||||
- [x] Manual testing completed"
|
||||
```
|
||||
|
||||
## Branch Cleanup
|
||||
|
||||
### After PR Merge
|
||||
```bash
|
||||
# Update develop
|
||||
git checkout develop
|
||||
git pull origin develop
|
||||
|
||||
# Delete local feature branch
|
||||
git branch -d feature/5391-praemie-checkout
|
||||
|
||||
# Delete remote branch (usually done by PR merge)
|
||||
git push origin --delete feature/5391-praemie-checkout
|
||||
```
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```bash
|
||||
# Branch naming
|
||||
feature/{task-id}-{description}
|
||||
bugfix/{task-id}-{description}
|
||||
hotfix/{task-id}-{description}
|
||||
|
||||
# Commit format
|
||||
<type>(<scope>): <description>
|
||||
|
||||
# Common types
|
||||
feat, fix, docs, style, refactor, perf, test, build, ci, chore
|
||||
|
||||
# PR target
|
||||
Always: develop (NOT main)
|
||||
|
||||
# Banned in commits
|
||||
- "Generated with Claude Code"
|
||||
- "Co-Authored-By: Claude"
|
||||
- Any AI attribution
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [Conventional Commits](https://www.conventionalcommits.org/)
|
||||
- Project PR template: `.github/pull_request_template.md`
|
||||
- Code review standards: `.github/review-instructions.md`
|
||||
|
||||
@@ -1,284 +1,277 @@
|
||||
---
|
||||
name: library-creator
|
||||
description: This skill should be used when creating feature/data-access/ui/util libraries or user says "create library", "new library", "scaffold library". Creates new Angular libraries in ISA-Frontend monorepo with proper Nx configuration, Vitest setup, architectural tags, and path aliases.
|
||||
---
|
||||
|
||||
# Library Creator
|
||||
|
||||
## Overview
|
||||
|
||||
Automate the creation of new Angular libraries following ISA-Frontend conventions. This skill handles the complete scaffolding workflow including Nx generation, Vitest configuration with CI/CD integration, architectural tags, path alias verification, and initial validation.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Invoke this skill when:
|
||||
- User requests creating a new library
|
||||
- User mentions "new library", "scaffold library", or "create feature"
|
||||
- User wants to add a new domain/layer/feature to the monorepo
|
||||
|
||||
## Required Parameters
|
||||
|
||||
Collect these parameters from the user:
|
||||
- **domain**: Domain name (oms, remission, checkout, ui, core, shared, utils)
|
||||
- **layer**: Layer type (feature, data-access, ui, util)
|
||||
- **name**: Library name in kebab-case
|
||||
|
||||
## Library Creation Workflow
|
||||
|
||||
### Step 1: Validate Input
|
||||
|
||||
**Verify Domain:**
|
||||
- Use `docs-researcher` to check `docs/library-reference.md`
|
||||
- Ensure domain follows existing patterns
|
||||
|
||||
**Validate Layer:**
|
||||
- Must be one of: feature, data-access, ui, util
|
||||
|
||||
**Check Name:**
|
||||
- Must be kebab-case
|
||||
- Must not conflict with existing libraries
|
||||
|
||||
**Determine Path Depth:**
|
||||
- 3 levels: `libs/domain/layer/name` → `../../../`
|
||||
- 4 levels: `libs/domain/type/layer/name` → `../../../../`
|
||||
|
||||
### Step 2: Run Dry-Run
|
||||
|
||||
Execute Nx generator with `--dry-run` to preview changes:
|
||||
|
||||
```bash
|
||||
npx nx generate @nx/angular:library \
|
||||
--name=[domain]-[layer]-[name] \
|
||||
--directory=libs/[domain]/[layer]/[name] \
|
||||
--importPath=@isa/[domain]/[layer]/[name] \
|
||||
--prefix=[domain] \
|
||||
--style=css \
|
||||
--unitTestRunner=vitest \
|
||||
--standalone=true \
|
||||
--skipTests=false \
|
||||
--dry-run
|
||||
```
|
||||
|
||||
Review output with user before proceeding.
|
||||
|
||||
### Step 3: Generate Library
|
||||
|
||||
Execute without `--dry-run`:
|
||||
|
||||
```bash
|
||||
npx nx generate @nx/angular:library \
|
||||
--name=[domain]-[layer]-[name] \
|
||||
--directory=libs/[domain]/[layer]/[name] \
|
||||
--importPath=@isa/[domain]/[layer]/[name] \
|
||||
--prefix=[domain] \
|
||||
--style=css \
|
||||
--unitTestRunner=vitest \
|
||||
--standalone=true \
|
||||
--skipTests=false
|
||||
```
|
||||
|
||||
### Step 4: Add Architectural Tags
|
||||
|
||||
**CRITICAL**: Immediately after library generation, add proper tags to `project.json` for `@nx/enforce-module-boundaries`.
|
||||
|
||||
Run the tagging script:
|
||||
```bash
|
||||
node scripts/add-library-tags.js
|
||||
```
|
||||
|
||||
Or manually add tags to `libs/[domain]/[layer]/[name]/project.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "[domain]-[layer]-[name]",
|
||||
"tags": [
|
||||
"scope:[domain]",
|
||||
"type:[layer]"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Tag Rules:**
|
||||
- **Scope tag**: `scope:[domain]` (e.g., `scope:oms`, `scope:crm`, `scope:ui`, `scope:shared`)
|
||||
- **Type tag**: `type:[layer]` (e.g., `type:feature`, `type:data-access`, `type:ui`, `type:util`)
|
||||
|
||||
**Examples:**
|
||||
- `libs/oms/feature/return-search` → `["scope:oms", "type:feature"]`
|
||||
- `libs/ui/buttons` → `["scope:ui", "type:ui"]`
|
||||
- `libs/shared/scanner` → `["scope:shared", "type:shared"]`
|
||||
- `libs/core/auth` → `["scope:core", "type:core"]`
|
||||
|
||||
**Verification:**
|
||||
```bash
|
||||
# Check tags were added
|
||||
cat libs/[domain]/[layer]/[name]/project.json | jq '.tags'
|
||||
|
||||
# Should output: ["scope:[domain]", "type:[layer]"]
|
||||
```
|
||||
|
||||
### Step 5: Configure Vitest with JUnit and Cobertura
|
||||
|
||||
Update `libs/[path]/vite.config.mts` with this template:
|
||||
|
||||
```typescript
|
||||
/// <reference types='vitest' />
|
||||
import { defineConfig } from 'vite';
|
||||
import angular from '@analogjs/vite-plugin-angular';
|
||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
|
||||
|
||||
export default
|
||||
// @ts-expect-error - Vitest reporter tuple types have complex inference issues
|
||||
defineConfig(() => ({
|
||||
root: __dirname,
|
||||
cacheDir: '../../../node_modules/.vite/libs/[path]',
|
||||
plugins: [angular(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
|
||||
test: {
|
||||
watch: false,
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
||||
setupFiles: ['src/test-setup.ts'],
|
||||
reporters: [
|
||||
'default',
|
||||
['junit', { outputFile: '../../../testresults/junit-[library-name].xml' }],
|
||||
],
|
||||
coverage: {
|
||||
reportsDirectory: '../../../coverage/libs/[path]',
|
||||
provider: 'v8' as const,
|
||||
reporter: ['text', 'cobertura'],
|
||||
},
|
||||
},
|
||||
}));
|
||||
```
|
||||
|
||||
**Critical**: Adjust path depth (`../../../` or `../../../../`) based on library location.
|
||||
|
||||
### Step 6: Verify Configuration
|
||||
|
||||
**Check Path Alias:**
|
||||
- Verify `tsconfig.base.json` was updated
|
||||
- Should have: `"@isa/[domain]/[layer]/[name]": ["libs/[domain]/[layer]/[name]/src/index.ts"]`
|
||||
|
||||
**Run Initial Test:**
|
||||
```bash
|
||||
npx nx test [library-name] --coverage.enabled=true --skip-nx-cache
|
||||
```
|
||||
|
||||
**Verify CI/CD Files Created:**
|
||||
- JUnit XML: `testresults/junit-[library-name].xml`
|
||||
- Cobertura XML: `coverage/libs/[path]/cobertura-coverage.xml`
|
||||
|
||||
### Step 7: Create Library README
|
||||
|
||||
Use `docs-researcher` to find similar library READMEs, then create comprehensive documentation including:
|
||||
- Overview and purpose
|
||||
- Installation/import instructions
|
||||
- API documentation
|
||||
- Usage examples
|
||||
- Testing information (Vitest + Angular Testing Utilities)
|
||||
|
||||
### Step 8: Update Library Reference
|
||||
|
||||
Add entry to `docs/library-reference.md` under appropriate domain:
|
||||
|
||||
```markdown
|
||||
#### `@isa/[domain]/[layer]/[name]`
|
||||
**Path:** `libs/[domain]/[layer]/[name]`
|
||||
**Type:** [Feature/Data Access/UI/Util]
|
||||
**Testing:** Vitest
|
||||
|
||||
[Brief description]
|
||||
```
|
||||
|
||||
### Step 9: Run Full Validation
|
||||
|
||||
Execute validation commands to ensure library is properly configured:
|
||||
|
||||
```bash
|
||||
# Lint (includes boundary checks)
|
||||
npx nx lint [library-name]
|
||||
|
||||
# Test with coverage
|
||||
npx nx test [library-name] --coverage.enabled=true --skip-nx-cache
|
||||
|
||||
# Build (if buildable)
|
||||
npx nx build [library-name]
|
||||
|
||||
# Dependency graph
|
||||
npx nx graph --focus=[library-name]
|
||||
```
|
||||
|
||||
### Step 10: Generate Creation Report
|
||||
|
||||
Provide this structured report to the user:
|
||||
|
||||
```
|
||||
Library Created Successfully
|
||||
============================
|
||||
|
||||
Library Name: [domain]-[layer]-[name]
|
||||
Path: libs/[domain]/[layer]/[name]
|
||||
Import Alias: @isa/[domain]/[layer]/[name]
|
||||
|
||||
✅ Configuration
|
||||
----------------
|
||||
Test Framework: Vitest with Angular Testing Utilities
|
||||
Style: CSS
|
||||
Standalone: Yes
|
||||
Tags: scope:[domain], type:[layer]
|
||||
JUnit Reporter: ✅ testresults/junit-[library-name].xml
|
||||
Cobertura Coverage: ✅ coverage/libs/[path]/cobertura-coverage.xml
|
||||
|
||||
📦 Import Statement
|
||||
-------------------
|
||||
import { Component } from '@isa/[domain]/[layer]/[name]';
|
||||
|
||||
🧪 Test Commands
|
||||
----------------
|
||||
npx nx test [library-name] --skip-nx-cache
|
||||
npx nx test [library-name] --coverage.enabled=true --skip-nx-cache
|
||||
|
||||
🏗️ Architecture Compliance
|
||||
--------------------------
|
||||
Tags enforce module boundaries via @nx/enforce-module-boundaries
|
||||
Run lint to check for violations: npx nx lint [library-name]
|
||||
|
||||
📝 Next Steps
|
||||
-------------
|
||||
1. Develop library features
|
||||
2. Write tests using Vitest + Angular Testing Utilities
|
||||
3. Add E2E attributes (data-what, data-which) to templates
|
||||
4. Update README with usage examples
|
||||
5. Follow architecture rules (see eslint.config.js for constraints)
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
**Path depth mismatch:**
|
||||
- Count directory levels from workspace root
|
||||
- Adjust `../` in outputFile and reportsDirectory
|
||||
|
||||
**TypeScript errors in vite.config.mts:**
|
||||
- Add `// @ts-expect-error` before `defineConfig()`
|
||||
|
||||
**Path alias not working:**
|
||||
- Check tsconfig.base.json
|
||||
- Run `npx nx reset`
|
||||
- Restart TypeScript server
|
||||
|
||||
**Library already exists:**
|
||||
- Check `tsconfig.base.json` for existing path alias
|
||||
- Use Grep to search for existing library name
|
||||
- Suggest alternative name to user
|
||||
|
||||
## References
|
||||
|
||||
- docs/guidelines/testing.md (Vitest, JUnit, Cobertura sections)
|
||||
- docs/library-reference.md (domain patterns)
|
||||
- CLAUDE.md (Library Organization, Testing Framework sections)
|
||||
- eslint.config.js (@nx/enforce-module-boundaries configuration)
|
||||
- scripts/add-library-tags.js (automatic tagging script)
|
||||
- .claude/skills/architecture-enforcer (boundary validation)
|
||||
- Nx Angular Library Generator: https://nx.dev/nx-api/angular/generators/library
|
||||
- Nx Enforce Module Boundaries: https://nx.dev/nx-api/eslint-plugin/documents/enforce-module-boundaries
|
||||
---
|
||||
name: library-creator
|
||||
description: This skill should be used when creating feature/data-access/ui/util libraries or user says "create library", "new library", "scaffold library". Creates new Angular libraries in ISA-Frontend monorepo with proper Nx configuration, Vitest setup, architectural tags, and path aliases.
|
||||
---
|
||||
|
||||
# Library Creator
|
||||
|
||||
## Overview
|
||||
|
||||
Automate the creation of new Angular libraries following ISA-Frontend conventions. This skill handles the complete scaffolding workflow including Nx generation, Vitest configuration with CI/CD integration, architectural tags, path alias verification, and initial validation.
|
||||
|
||||
## Required Parameters
|
||||
|
||||
Collect these parameters from the user:
|
||||
- **domain**: Domain name (oms, remission, checkout, ui, core, shared, utils)
|
||||
- **layer**: Layer type (feature, data-access, ui, util)
|
||||
- **name**: Library name in kebab-case
|
||||
|
||||
## Library Creation Workflow
|
||||
|
||||
### Step 1: Validate Input
|
||||
|
||||
**Verify Domain:**
|
||||
- Use `docs-researcher` to check `docs/library-reference.md`
|
||||
- Ensure domain follows existing patterns
|
||||
|
||||
**Validate Layer:**
|
||||
- Must be one of: feature, data-access, ui, util
|
||||
|
||||
**Check Name:**
|
||||
- Must be kebab-case
|
||||
- Must not conflict with existing libraries
|
||||
|
||||
**Determine Path Depth:**
|
||||
- 3 levels: `libs/domain/layer/name` → `../../../`
|
||||
- 4 levels: `libs/domain/type/layer/name` → `../../../../`
|
||||
|
||||
### Step 2: Run Dry-Run
|
||||
|
||||
Execute Nx generator with `--dry-run` to preview changes:
|
||||
|
||||
```bash
|
||||
npx nx generate @nx/angular:library \
|
||||
--name=[domain]-[layer]-[name] \
|
||||
--directory=libs/[domain]/[layer]/[name] \
|
||||
--importPath=@isa/[domain]/[layer]/[name] \
|
||||
--prefix=[domain] \
|
||||
--style=css \
|
||||
--unitTestRunner=vitest \
|
||||
--standalone=true \
|
||||
--skipTests=false \
|
||||
--dry-run
|
||||
```
|
||||
|
||||
Review output with user before proceeding.
|
||||
|
||||
### Step 3: Generate Library
|
||||
|
||||
Execute without `--dry-run`:
|
||||
|
||||
```bash
|
||||
npx nx generate @nx/angular:library \
|
||||
--name=[domain]-[layer]-[name] \
|
||||
--directory=libs/[domain]/[layer]/[name] \
|
||||
--importPath=@isa/[domain]/[layer]/[name] \
|
||||
--prefix=[domain] \
|
||||
--style=css \
|
||||
--unitTestRunner=vitest \
|
||||
--standalone=true \
|
||||
--skipTests=false
|
||||
```
|
||||
|
||||
### Step 4: Add Architectural Tags
|
||||
|
||||
**CRITICAL**: Immediately after library generation, add proper tags to `project.json` for `@nx/enforce-module-boundaries`.
|
||||
|
||||
Run the tagging script:
|
||||
```bash
|
||||
node scripts/add-library-tags.js
|
||||
```
|
||||
|
||||
Or manually add tags to `libs/[domain]/[layer]/[name]/project.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "[domain]-[layer]-[name]",
|
||||
"tags": [
|
||||
"scope:[domain]",
|
||||
"type:[layer]"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Tag Rules:**
|
||||
- **Scope tag**: `scope:[domain]` (e.g., `scope:oms`, `scope:crm`, `scope:ui`, `scope:shared`)
|
||||
- **Type tag**: `type:[layer]` (e.g., `type:feature`, `type:data-access`, `type:ui`, `type:util`)
|
||||
|
||||
**Examples:**
|
||||
- `libs/oms/feature/return-search` → `["scope:oms", "type:feature"]`
|
||||
- `libs/ui/buttons` → `["scope:ui", "type:ui"]`
|
||||
- `libs/shared/scanner` → `["scope:shared", "type:shared"]`
|
||||
- `libs/core/auth` → `["scope:core", "type:core"]`
|
||||
|
||||
**Verification:**
|
||||
```bash
|
||||
# Check tags were added
|
||||
cat libs/[domain]/[layer]/[name]/project.json | jq '.tags'
|
||||
|
||||
# Should output: ["scope:[domain]", "type:[layer]"]
|
||||
```
|
||||
|
||||
### Step 5: Configure Vitest with JUnit and Cobertura
|
||||
|
||||
Update `libs/[path]/vite.config.mts` with this template:
|
||||
|
||||
```typescript
|
||||
/// <reference types='vitest' />
|
||||
import { defineConfig } from 'vite';
|
||||
import angular from '@analogjs/vite-plugin-angular';
|
||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
|
||||
|
||||
export default
|
||||
// @ts-expect-error - Vitest reporter tuple types have complex inference issues
|
||||
defineConfig(() => ({
|
||||
root: __dirname,
|
||||
cacheDir: '../../../node_modules/.vite/libs/[path]',
|
||||
plugins: [angular(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
|
||||
test: {
|
||||
watch: false,
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
||||
setupFiles: ['src/test-setup.ts'],
|
||||
reporters: [
|
||||
'default',
|
||||
['junit', { outputFile: '../../../testresults/junit-[library-name].xml' }],
|
||||
],
|
||||
coverage: {
|
||||
reportsDirectory: '../../../coverage/libs/[path]',
|
||||
provider: 'v8' as const,
|
||||
reporter: ['text', 'cobertura'],
|
||||
},
|
||||
},
|
||||
}));
|
||||
```
|
||||
|
||||
**Critical**: Adjust path depth (`../../../` or `../../../../`) based on library location.
|
||||
|
||||
### Step 6: Verify Configuration
|
||||
|
||||
**Check Path Alias:**
|
||||
- Verify `tsconfig.base.json` was updated
|
||||
- Should have: `"@isa/[domain]/[layer]/[name]": ["libs/[domain]/[layer]/[name]/src/index.ts"]`
|
||||
|
||||
**Run Initial Test:**
|
||||
```bash
|
||||
npx nx test [library-name] --coverage.enabled=true --skip-nx-cache
|
||||
```
|
||||
|
||||
**Verify CI/CD Files Created:**
|
||||
- JUnit XML: `testresults/junit-[library-name].xml`
|
||||
- Cobertura XML: `coverage/libs/[path]/cobertura-coverage.xml`
|
||||
|
||||
### Step 7: Create Library README
|
||||
|
||||
Use `docs-researcher` to find similar library READMEs, then create comprehensive documentation including:
|
||||
- Overview and purpose
|
||||
- Installation/import instructions
|
||||
- API documentation
|
||||
- Usage examples
|
||||
- Testing information (Vitest + Angular Testing Utilities)
|
||||
|
||||
### Step 8: Update Library Reference
|
||||
|
||||
Add entry to `docs/library-reference.md` under appropriate domain:
|
||||
|
||||
```markdown
|
||||
#### `@isa/[domain]/[layer]/[name]`
|
||||
**Path:** `libs/[domain]/[layer]/[name]`
|
||||
**Type:** [Feature/Data Access/UI/Util]
|
||||
**Testing:** Vitest
|
||||
|
||||
[Brief description]
|
||||
```
|
||||
|
||||
### Step 9: Run Full Validation
|
||||
|
||||
Execute validation commands to ensure library is properly configured:
|
||||
|
||||
```bash
|
||||
# Lint (includes boundary checks)
|
||||
npx nx lint [library-name]
|
||||
|
||||
# Test with coverage
|
||||
npx nx test [library-name] --coverage.enabled=true --skip-nx-cache
|
||||
|
||||
# Build (if buildable)
|
||||
npx nx build [library-name]
|
||||
|
||||
# Dependency graph
|
||||
npx nx graph --focus=[library-name]
|
||||
```
|
||||
|
||||
### Step 10: Generate Creation Report
|
||||
|
||||
Provide this structured report to the user:
|
||||
|
||||
```
|
||||
Library Created Successfully
|
||||
============================
|
||||
|
||||
Library Name: [domain]-[layer]-[name]
|
||||
Path: libs/[domain]/[layer]/[name]
|
||||
Import Alias: @isa/[domain]/[layer]/[name]
|
||||
|
||||
✅ Configuration
|
||||
----------------
|
||||
Test Framework: Vitest with Angular Testing Utilities
|
||||
Style: CSS
|
||||
Standalone: Yes
|
||||
Tags: scope:[domain], type:[layer]
|
||||
JUnit Reporter: ✅ testresults/junit-[library-name].xml
|
||||
Cobertura Coverage: ✅ coverage/libs/[path]/cobertura-coverage.xml
|
||||
|
||||
📦 Import Statement
|
||||
-------------------
|
||||
import { Component } from '@isa/[domain]/[layer]/[name]';
|
||||
|
||||
🧪 Test Commands
|
||||
----------------
|
||||
npx nx test [library-name] --skip-nx-cache
|
||||
npx nx test [library-name] --coverage.enabled=true --skip-nx-cache
|
||||
|
||||
🏗️ Architecture Compliance
|
||||
--------------------------
|
||||
Tags enforce module boundaries via @nx/enforce-module-boundaries
|
||||
Run lint to check for violations: npx nx lint [library-name]
|
||||
|
||||
📝 Next Steps
|
||||
-------------
|
||||
1. Develop library features
|
||||
2. Write tests using Vitest + Angular Testing Utilities
|
||||
3. Add E2E attributes (data-what, data-which) to templates
|
||||
4. Update README with usage examples
|
||||
5. Follow architecture rules (see eslint.config.js for constraints)
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
**Path depth mismatch:**
|
||||
- Count directory levels from workspace root
|
||||
- Adjust `../` in outputFile and reportsDirectory
|
||||
|
||||
**TypeScript errors in vite.config.mts:**
|
||||
- Add `// @ts-expect-error` before `defineConfig()`
|
||||
|
||||
**Path alias not working:**
|
||||
- Check tsconfig.base.json
|
||||
- Run `npx nx reset`
|
||||
- Restart TypeScript server
|
||||
|
||||
**Library already exists:**
|
||||
- Check `tsconfig.base.json` for existing path alias
|
||||
- Use Grep to search for existing library name
|
||||
- Suggest alternative name to user
|
||||
|
||||
## References
|
||||
|
||||
- docs/guidelines/testing.md (Vitest, JUnit, Cobertura sections)
|
||||
- docs/library-reference.md (domain patterns)
|
||||
- CLAUDE.md (Library Organization, Testing Framework sections)
|
||||
- eslint.config.js (@nx/enforce-module-boundaries configuration)
|
||||
- scripts/add-library-tags.js (automatic tagging script)
|
||||
- .claude/skills/architecture-validator (boundary validation)
|
||||
- Nx Angular Library Generator: https://nx.dev/nx-api/angular/generators/library
|
||||
- Nx Enforce Module Boundaries: https://nx.dev/nx-api/eslint-plugin/documents/enforce-module-boundaries
|
||||
|
||||
@@ -1,272 +1,234 @@
|
||||
---
|
||||
name: logging
|
||||
description: This skill should be used when working with Angular components, directives, services, pipes, guards, or TypeScript classes. Logging is MANDATORY in all Angular files. Implements @isa/core/logging with logger() factory pattern, appropriate log levels, lazy evaluation for performance, error handling, and avoids console.log and common mistakes.
|
||||
---
|
||||
|
||||
# Logging Helper Skill
|
||||
|
||||
Ensures consistent and efficient logging using `@isa/core/logging` library.
|
||||
|
||||
## When to Use
|
||||
|
||||
- Adding logging to new components/services
|
||||
- Refactoring existing logging code
|
||||
- Reviewing code for proper logging patterns
|
||||
- Debugging logging issues
|
||||
|
||||
## Core Principles
|
||||
|
||||
### 1. Always Use Factory Pattern
|
||||
|
||||
```typescript
|
||||
import { logger } from '@isa/core/logging';
|
||||
|
||||
// ✅ DO
|
||||
#logger = logger();
|
||||
|
||||
// ❌ DON'T
|
||||
constructor(private loggingService: LoggingService) {}
|
||||
```
|
||||
|
||||
### 2. Choose Appropriate Log Levels
|
||||
|
||||
- **Trace**: Fine-grained debugging (method entry/exit)
|
||||
- **Debug**: Development debugging (variable states)
|
||||
- **Info**: Runtime information (user actions, events)
|
||||
- **Warn**: Potentially harmful situations
|
||||
- **Error**: Errors affecting functionality
|
||||
|
||||
### 3. Context Patterns
|
||||
|
||||
**Static Context** (component level):
|
||||
```typescript
|
||||
#logger = logger({ component: 'UserProfileComponent' });
|
||||
```
|
||||
|
||||
**Dynamic Context** (instance level):
|
||||
```typescript
|
||||
#logger = logger(() => ({
|
||||
userId: this.authService.currentUserId,
|
||||
storeId: this.config.storeId
|
||||
}));
|
||||
```
|
||||
|
||||
**Message Context** (use functions for performance):
|
||||
```typescript
|
||||
// ✅ Recommended - lazy evaluation
|
||||
this.#logger.info('Order processed', () => ({
|
||||
orderId: order.id,
|
||||
total: order.total,
|
||||
timestamp: Date.now()
|
||||
}));
|
||||
|
||||
// ✅ Acceptable - static values
|
||||
this.#logger.info('Order processed', {
|
||||
orderId: order.id,
|
||||
status: 'completed'
|
||||
});
|
||||
```
|
||||
|
||||
## Essential Patterns
|
||||
|
||||
### Component Logging
|
||||
```typescript
|
||||
@Component({
|
||||
selector: 'app-product-list',
|
||||
standalone: true,
|
||||
})
|
||||
export class ProductListComponent {
|
||||
#logger = logger({ component: 'ProductListComponent' });
|
||||
|
||||
ngOnInit(): void {
|
||||
this.#logger.info('Component initialized');
|
||||
}
|
||||
|
||||
onAction(id: string): void {
|
||||
this.#logger.debug('Action triggered', { id });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Service Logging
|
||||
```typescript
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DataService {
|
||||
#logger = logger({ service: 'DataService' });
|
||||
|
||||
fetchData(endpoint: string): Observable<Data> {
|
||||
this.#logger.debug('Fetching data', { endpoint });
|
||||
|
||||
return this.http.get<Data>(endpoint).pipe(
|
||||
tap((data) => this.#logger.info('Data fetched', () => ({
|
||||
endpoint,
|
||||
size: data.length
|
||||
}))),
|
||||
catchError((error) => {
|
||||
this.#logger.error('Fetch failed', error, () => ({
|
||||
endpoint,
|
||||
status: error.status
|
||||
}));
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
```typescript
|
||||
try {
|
||||
await this.processOrder(orderId);
|
||||
} catch (error) {
|
||||
this.#logger.error('Order processing failed', error as Error, () => ({
|
||||
orderId,
|
||||
step: this.currentStep,
|
||||
attemptNumber: this.retryCount
|
||||
}));
|
||||
throw error;
|
||||
}
|
||||
```
|
||||
|
||||
### Hierarchical Context
|
||||
```typescript
|
||||
@Component({
|
||||
providers: [
|
||||
provideLoggerContext({ feature: 'checkout', module: 'sales' })
|
||||
]
|
||||
})
|
||||
export class CheckoutComponent {
|
||||
#logger = logger(() => ({ userId: this.userService.currentUserId }));
|
||||
|
||||
// Logs include: feature, module, userId + message context
|
||||
}
|
||||
```
|
||||
|
||||
## Common Mistakes to Avoid
|
||||
|
||||
```typescript
|
||||
// ❌ Don't use console.log
|
||||
console.log('User logged in');
|
||||
// ✅ Use logger
|
||||
this.#logger.info('User logged in');
|
||||
|
||||
// ❌ Don't create expensive context eagerly
|
||||
this.#logger.debug('Processing', {
|
||||
data: this.computeExpensive() // Always executes
|
||||
});
|
||||
// ✅ Use function for lazy evaluation
|
||||
this.#logger.debug('Processing', () => ({
|
||||
data: this.computeExpensive() // Only if debug enabled
|
||||
}));
|
||||
|
||||
// ❌ Don't log in tight loops
|
||||
for (const item of items) {
|
||||
this.#logger.debug('Item', { item });
|
||||
}
|
||||
// ✅ Log aggregates
|
||||
this.#logger.debug('Batch processed', () => ({
|
||||
count: items.length
|
||||
}));
|
||||
|
||||
// ❌ Don't log sensitive data
|
||||
this.#logger.info('User auth', { password: user.password });
|
||||
// ✅ Log safe identifiers only
|
||||
this.#logger.info('User auth', { userId: user.id });
|
||||
|
||||
// ❌ Don't miss error object
|
||||
this.#logger.error('Failed');
|
||||
// ✅ Include error object
|
||||
this.#logger.error('Failed', error as Error);
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### App Configuration
|
||||
```typescript
|
||||
// app.config.ts
|
||||
import { ApplicationConfig, isDevMode } from '@angular/core';
|
||||
import {
|
||||
provideLogging, withLogLevel, withSink,
|
||||
LogLevel, ConsoleLogSink
|
||||
} from '@isa/core/logging';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideLogging(
|
||||
withLogLevel(isDevMode() ? LogLevel.Debug : LogLevel.Warn),
|
||||
withSink(ConsoleLogSink),
|
||||
withContext({ app: 'ISA', version: '1.0.0' })
|
||||
)
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```typescript
|
||||
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
|
||||
import { LoggingService } from '@isa/core/logging';
|
||||
|
||||
describe('MyComponent', () => {
|
||||
const createComponent = createComponentFactory({
|
||||
component: MyComponent,
|
||||
mocks: [LoggingService]
|
||||
});
|
||||
|
||||
it('should log error', () => {
|
||||
const spectator = createComponent();
|
||||
const loggingService = spectator.inject(LoggingService);
|
||||
|
||||
spectator.component.riskyOperation();
|
||||
|
||||
expect(loggingService.error).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expect.any(Error),
|
||||
expect.any(Function)
|
||||
);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Code Review Checklist
|
||||
|
||||
- [ ] Uses `logger()` factory, not `LoggingService` injection
|
||||
- [ ] Appropriate log level for each message
|
||||
- [ ] Context functions for expensive operations
|
||||
- [ ] No sensitive information (passwords, tokens, PII)
|
||||
- [ ] No `console.log` statements
|
||||
- [ ] Error logs include error object
|
||||
- [ ] No logging in tight loops
|
||||
- [ ] Component/service identified in context
|
||||
- [ ] E2E attributes on interactive elements
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```typescript
|
||||
// Import
|
||||
import { logger, provideLoggerContext } from '@isa/core/logging';
|
||||
|
||||
// Create logger
|
||||
#logger = logger(); // Basic
|
||||
#logger = logger({ component: 'Name' }); // Static context
|
||||
#logger = logger(() => ({ id: this.id })); // Dynamic context
|
||||
|
||||
// Log messages
|
||||
this.#logger.trace('Detailed trace');
|
||||
this.#logger.debug('Debug info');
|
||||
this.#logger.info('General info', () => ({ key: value }));
|
||||
this.#logger.warn('Warning');
|
||||
this.#logger.error('Error', error, () => ({ context }));
|
||||
|
||||
// Component context
|
||||
@Component({
|
||||
providers: [provideLoggerContext({ feature: 'users' })]
|
||||
})
|
||||
```
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- Full documentation: `libs/core/logging/README.md`
|
||||
- Examples: `.claude/skills/logging-helper/examples.md`
|
||||
- Quick reference: `.claude/skills/logging-helper/reference.md`
|
||||
- Troubleshooting: `.claude/skills/logging-helper/troubleshooting.md`
|
||||
---
|
||||
name: logging
|
||||
description: This skill should be used when working with Angular components, directives, services, pipes, guards, or TypeScript classes. Logging is MANDATORY in all Angular files. Implements @isa/core/logging with logger() factory pattern, appropriate log levels, lazy evaluation for performance, error handling, and avoids console.log and common mistakes.
|
||||
---
|
||||
|
||||
# Logging
|
||||
|
||||
Ensures consistent and efficient logging using `@isa/core/logging` library.
|
||||
|
||||
## Core Principles
|
||||
|
||||
### 1. Always Use Factory Pattern
|
||||
|
||||
```typescript
|
||||
import { logger } from '@isa/core/logging';
|
||||
|
||||
// ✅ DO
|
||||
#logger = logger();
|
||||
|
||||
// ❌ DON'T
|
||||
constructor(private loggingService: LoggingService) {}
|
||||
```
|
||||
|
||||
### 2. Choose Appropriate Log Levels
|
||||
|
||||
- **Trace**: Fine-grained debugging (method entry/exit)
|
||||
- **Debug**: Development debugging (variable states)
|
||||
- **Info**: Runtime information (user actions, events)
|
||||
- **Warn**: Potentially harmful situations
|
||||
- **Error**: Errors affecting functionality
|
||||
|
||||
### 3. Context Patterns
|
||||
|
||||
**Static Context** (component level):
|
||||
```typescript
|
||||
#logger = logger({ component: 'UserProfileComponent' });
|
||||
```
|
||||
|
||||
**Dynamic Context** (instance level):
|
||||
```typescript
|
||||
#logger = logger(() => ({
|
||||
userId: this.authService.currentUserId,
|
||||
storeId: this.config.storeId
|
||||
}));
|
||||
```
|
||||
|
||||
**Message Context** (use functions for performance):
|
||||
```typescript
|
||||
// ✅ Recommended - lazy evaluation
|
||||
this.#logger.info('Order processed', () => ({
|
||||
orderId: order.id,
|
||||
total: order.total,
|
||||
timestamp: Date.now()
|
||||
}));
|
||||
|
||||
// ✅ Acceptable - static values
|
||||
this.#logger.info('Order processed', {
|
||||
orderId: order.id,
|
||||
status: 'completed'
|
||||
});
|
||||
```
|
||||
|
||||
## Essential Patterns
|
||||
|
||||
### Component Logging
|
||||
```typescript
|
||||
@Component({
|
||||
selector: 'app-product-list',
|
||||
standalone: true,
|
||||
})
|
||||
export class ProductListComponent {
|
||||
#logger = logger({ component: 'ProductListComponent' });
|
||||
|
||||
ngOnInit(): void {
|
||||
this.#logger.info('Component initialized');
|
||||
}
|
||||
|
||||
onAction(id: string): void {
|
||||
this.#logger.debug('Action triggered', { id });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Service Logging
|
||||
```typescript
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DataService {
|
||||
#logger = logger({ service: 'DataService' });
|
||||
|
||||
fetchData(endpoint: string): Observable<Data> {
|
||||
this.#logger.debug('Fetching data', { endpoint });
|
||||
|
||||
return this.http.get<Data>(endpoint).pipe(
|
||||
tap((data) => this.#logger.info('Data fetched', () => ({
|
||||
endpoint,
|
||||
size: data.length
|
||||
}))),
|
||||
catchError((error) => {
|
||||
this.#logger.error('Fetch failed', error, () => ({
|
||||
endpoint,
|
||||
status: error.status
|
||||
}));
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
```typescript
|
||||
try {
|
||||
await this.processOrder(orderId);
|
||||
} catch (error) {
|
||||
this.#logger.error('Order processing failed', error as Error, () => ({
|
||||
orderId,
|
||||
step: this.currentStep,
|
||||
attemptNumber: this.retryCount
|
||||
}));
|
||||
throw error;
|
||||
}
|
||||
```
|
||||
|
||||
### Hierarchical Context
|
||||
```typescript
|
||||
@Component({
|
||||
providers: [
|
||||
provideLoggerContext({ feature: 'checkout', module: 'sales' })
|
||||
]
|
||||
})
|
||||
export class CheckoutComponent {
|
||||
#logger = logger(() => ({ userId: this.userService.currentUserId }));
|
||||
|
||||
// Logs include: feature, module, userId + message context
|
||||
}
|
||||
```
|
||||
|
||||
## Common Mistakes to Avoid
|
||||
|
||||
```typescript
|
||||
// ❌ Don't use console.log
|
||||
console.log('User logged in');
|
||||
// ✅ Use logger
|
||||
this.#logger.info('User logged in');
|
||||
|
||||
// ❌ Don't create expensive context eagerly
|
||||
this.#logger.debug('Processing', {
|
||||
data: this.computeExpensive() // Always executes
|
||||
});
|
||||
// ✅ Use function for lazy evaluation
|
||||
this.#logger.debug('Processing', () => ({
|
||||
data: this.computeExpensive() // Only if debug enabled
|
||||
}));
|
||||
|
||||
// ❌ Don't log in tight loops
|
||||
for (const item of items) {
|
||||
this.#logger.debug('Item', { item });
|
||||
}
|
||||
// ✅ Log aggregates
|
||||
this.#logger.debug('Batch processed', () => ({
|
||||
count: items.length
|
||||
}));
|
||||
|
||||
// ❌ Don't log sensitive data
|
||||
this.#logger.info('User auth', { password: user.password });
|
||||
// ✅ Log safe identifiers only
|
||||
this.#logger.info('User auth', { userId: user.id });
|
||||
|
||||
// ❌ Don't miss error object
|
||||
this.#logger.error('Failed');
|
||||
// ✅ Include error object
|
||||
this.#logger.error('Failed', error as Error);
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### App Configuration
|
||||
```typescript
|
||||
// app.config.ts
|
||||
import { ApplicationConfig, isDevMode } from '@angular/core';
|
||||
import {
|
||||
provideLogging, withLogLevel, withSink,
|
||||
LogLevel, ConsoleLogSink
|
||||
} from '@isa/core/logging';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideLogging(
|
||||
withLogLevel(isDevMode() ? LogLevel.Debug : LogLevel.Warn),
|
||||
withSink(ConsoleLogSink),
|
||||
withContext({ app: 'ISA', version: '1.0.0' })
|
||||
)
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```typescript
|
||||
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
|
||||
import { LoggingService } from '@isa/core/logging';
|
||||
|
||||
describe('MyComponent', () => {
|
||||
const createComponent = createComponentFactory({
|
||||
component: MyComponent,
|
||||
mocks: [LoggingService]
|
||||
});
|
||||
|
||||
it('should log error', () => {
|
||||
const spectator = createComponent();
|
||||
const loggingService = spectator.inject(LoggingService);
|
||||
|
||||
spectator.component.riskyOperation();
|
||||
|
||||
expect(loggingService.error).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expect.any(Error),
|
||||
expect.any(Function)
|
||||
);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Additional Resources
|
||||
|
||||
**For more detailed information:**
|
||||
|
||||
- **API signatures and patterns**: See [references/api-reference.md](references/api-reference.md) for complete API documentation
|
||||
- **Real-world examples**: See [references/examples.md](references/examples.md) for components, services, guards, interceptors, and more
|
||||
- **Troubleshooting**: See [references/troubleshooting.md](references/troubleshooting.md) for common issues and solutions
|
||||
|
||||
**Project documentation:**
|
||||
|
||||
- Full library documentation: `libs/core/logging/README.md`
|
||||
|
||||
@@ -1,192 +1,192 @@
|
||||
# Logging Quick Reference
|
||||
|
||||
## API Signatures
|
||||
|
||||
```typescript
|
||||
// Factory
|
||||
function logger(ctx?: MaybeLoggerContextFn): LoggerApi
|
||||
|
||||
// Logger API
|
||||
interface LoggerApi {
|
||||
trace(message: string, context?: MaybeLoggerContextFn): void;
|
||||
debug(message: string, context?: MaybeLoggerContextFn): void;
|
||||
info(message: string, context?: MaybeLoggerContextFn): void;
|
||||
warn(message: string, context?: MaybeLoggerContextFn): void;
|
||||
error(message: string, error?: Error, context?: MaybeLoggerContextFn): void;
|
||||
}
|
||||
|
||||
// Types
|
||||
type MaybeLoggerContextFn = LoggerContext | (() => LoggerContext);
|
||||
interface LoggerContext { [key: string]: unknown; }
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
| Pattern | Code |
|
||||
|---------|------|
|
||||
| Basic logger | `#logger = logger()` |
|
||||
| Static context | `#logger = logger({ component: 'Name' })` |
|
||||
| Dynamic context | `#logger = logger(() => ({ id: this.id }))` |
|
||||
| Log info | `this.#logger.info('Message')` |
|
||||
| Log with context | `this.#logger.info('Message', () => ({ key: value }))` |
|
||||
| Log error | `this.#logger.error('Error', error)` |
|
||||
| Error with context | `this.#logger.error('Error', error, () => ({ id }))` |
|
||||
| Component context | `providers: [provideLoggerContext({ feature: 'x' })]` |
|
||||
|
||||
## Configuration
|
||||
|
||||
```typescript
|
||||
// app.config.ts
|
||||
import { provideLogging, withLogLevel, withSink, withContext,
|
||||
LogLevel, ConsoleLogSink } from '@isa/core/logging';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideLogging(
|
||||
withLogLevel(isDevMode() ? LogLevel.Debug : LogLevel.Warn),
|
||||
withSink(ConsoleLogSink),
|
||||
withContext({ app: 'ISA', version: '1.0.0' })
|
||||
)
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
## Log Levels
|
||||
|
||||
| Level | Use Case | Example |
|
||||
|-------|----------|---------|
|
||||
| `Trace` | Method entry/exit | `this.#logger.trace('Entering processData')` |
|
||||
| `Debug` | Development info | `this.#logger.debug('Variable state', () => ({ x }))` |
|
||||
| `Info` | Runtime events | `this.#logger.info('User logged in', { userId })` |
|
||||
| `Warn` | Warnings | `this.#logger.warn('Deprecated API used')` |
|
||||
| `Error` | Errors | `this.#logger.error('Operation failed', error)` |
|
||||
| `Off` | Disable logging | `withLogLevel(LogLevel.Off)` |
|
||||
|
||||
## Decision Trees
|
||||
|
||||
### Context Type Decision
|
||||
```
|
||||
Value changes at runtime?
|
||||
├─ Yes → () => ({ value: this.getValue() })
|
||||
└─ No → { value: 'static' }
|
||||
|
||||
Computing value is expensive?
|
||||
├─ Yes → () => ({ data: this.compute() })
|
||||
└─ No → Either works
|
||||
```
|
||||
|
||||
### Log Level Decision
|
||||
```
|
||||
Method flow details? → Trace
|
||||
Development debug? → Debug
|
||||
Runtime information? → Info
|
||||
Potential problem? → Warn
|
||||
Error occurred? → Error
|
||||
```
|
||||
|
||||
## Performance Tips
|
||||
|
||||
```typescript
|
||||
// ✅ DO: Lazy evaluation
|
||||
this.#logger.debug('Data', () => ({
|
||||
result: this.expensive() // Only runs if debug enabled
|
||||
}));
|
||||
|
||||
// ❌ DON'T: Eager evaluation
|
||||
this.#logger.debug('Data', {
|
||||
result: this.expensive() // Always runs
|
||||
});
|
||||
|
||||
// ✅ DO: Log aggregates
|
||||
this.#logger.info('Batch done', () => ({ count: items.length }));
|
||||
|
||||
// ❌ DON'T: Log in loops
|
||||
for (const item of items) {
|
||||
this.#logger.debug('Item', { item }); // Performance hit
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```typescript
|
||||
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
|
||||
import { LoggingService } from '@isa/core/logging';
|
||||
|
||||
describe('MyComponent', () => {
|
||||
const createComponent = createComponentFactory({
|
||||
component: MyComponent,
|
||||
mocks: [LoggingService]
|
||||
});
|
||||
|
||||
it('logs error', () => {
|
||||
const spectator = createComponent();
|
||||
const logger = spectator.inject(LoggingService);
|
||||
|
||||
spectator.component.operation();
|
||||
|
||||
expect(logger.error).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Custom Sink
|
||||
|
||||
```typescript
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Sink, LogLevel, LoggerContext } from '@isa/core/logging';
|
||||
|
||||
@Injectable()
|
||||
export class CustomSink implements Sink {
|
||||
log(level: LogLevel, message: string, context?: LoggerContext, error?: Error): void {
|
||||
// Implementation
|
||||
}
|
||||
}
|
||||
|
||||
// Register
|
||||
provideLogging(withSink(CustomSink))
|
||||
```
|
||||
|
||||
## Sink Function (with DI)
|
||||
|
||||
```typescript
|
||||
import { inject } from '@angular/core';
|
||||
import { SinkFn, LogLevel } from '@isa/core/logging';
|
||||
|
||||
export const remoteSink: SinkFn = () => {
|
||||
const http = inject(HttpClient);
|
||||
|
||||
return (level, message, context, error) => {
|
||||
if (level === LogLevel.Error) {
|
||||
http.post('/api/logs', { level, message, context, error }).subscribe();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Register
|
||||
provideLogging(withSinkFn(remoteSink))
|
||||
```
|
||||
|
||||
## Common Imports
|
||||
|
||||
```typescript
|
||||
// Main imports
|
||||
import { logger, provideLoggerContext } from '@isa/core/logging';
|
||||
|
||||
// Configuration imports
|
||||
import {
|
||||
provideLogging,
|
||||
withLogLevel,
|
||||
withSink,
|
||||
withContext,
|
||||
LogLevel,
|
||||
ConsoleLogSink
|
||||
} from '@isa/core/logging';
|
||||
|
||||
// Type imports
|
||||
import {
|
||||
LoggerApi,
|
||||
Sink,
|
||||
SinkFn,
|
||||
LoggerContext
|
||||
} from '@isa/core/logging';
|
||||
```
|
||||
# Logging API Reference
|
||||
|
||||
## API Signatures
|
||||
|
||||
```typescript
|
||||
// Factory
|
||||
function logger(ctx?: MaybeLoggerContextFn): LoggerApi
|
||||
|
||||
// Logger API
|
||||
interface LoggerApi {
|
||||
trace(message: string, context?: MaybeLoggerContextFn): void;
|
||||
debug(message: string, context?: MaybeLoggerContextFn): void;
|
||||
info(message: string, context?: MaybeLoggerContextFn): void;
|
||||
warn(message: string, context?: MaybeLoggerContextFn): void;
|
||||
error(message: string, error?: Error, context?: MaybeLoggerContextFn): void;
|
||||
}
|
||||
|
||||
// Types
|
||||
type MaybeLoggerContextFn = LoggerContext | (() => LoggerContext);
|
||||
interface LoggerContext { [key: string]: unknown; }
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
| Pattern | Code |
|
||||
|---------|------|
|
||||
| Basic logger | `#logger = logger()` |
|
||||
| Static context | `#logger = logger({ component: 'Name' })` |
|
||||
| Dynamic context | `#logger = logger(() => ({ id: this.id }))` |
|
||||
| Log info | `this.#logger.info('Message')` |
|
||||
| Log with context | `this.#logger.info('Message', () => ({ key: value }))` |
|
||||
| Log error | `this.#logger.error('Error', error)` |
|
||||
| Error with context | `this.#logger.error('Error', error, () => ({ id }))` |
|
||||
| Component context | `providers: [provideLoggerContext({ feature: 'x' })]` |
|
||||
|
||||
## Configuration
|
||||
|
||||
```typescript
|
||||
// app.config.ts
|
||||
import { provideLogging, withLogLevel, withSink, withContext,
|
||||
LogLevel, ConsoleLogSink } from '@isa/core/logging';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideLogging(
|
||||
withLogLevel(isDevMode() ? LogLevel.Debug : LogLevel.Warn),
|
||||
withSink(ConsoleLogSink),
|
||||
withContext({ app: 'ISA', version: '1.0.0' })
|
||||
)
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
## Log Levels
|
||||
|
||||
| Level | Use Case | Example |
|
||||
|-------|----------|---------|
|
||||
| `Trace` | Method entry/exit | `this.#logger.trace('Entering processData')` |
|
||||
| `Debug` | Development info | `this.#logger.debug('Variable state', () => ({ x }))` |
|
||||
| `Info` | Runtime events | `this.#logger.info('User logged in', { userId })` |
|
||||
| `Warn` | Warnings | `this.#logger.warn('Deprecated API used')` |
|
||||
| `Error` | Errors | `this.#logger.error('Operation failed', error)` |
|
||||
| `Off` | Disable logging | `withLogLevel(LogLevel.Off)` |
|
||||
|
||||
## Decision Trees
|
||||
|
||||
### Context Type Decision
|
||||
```
|
||||
Value changes at runtime?
|
||||
├─ Yes → () => ({ value: this.getValue() })
|
||||
└─ No → { value: 'static' }
|
||||
|
||||
Computing value is expensive?
|
||||
├─ Yes → () => ({ data: this.compute() })
|
||||
└─ No → Either works
|
||||
```
|
||||
|
||||
### Log Level Decision
|
||||
```
|
||||
Method flow details? → Trace
|
||||
Development debug? → Debug
|
||||
Runtime information? → Info
|
||||
Potential problem? → Warn
|
||||
Error occurred? → Error
|
||||
```
|
||||
|
||||
## Performance Tips
|
||||
|
||||
```typescript
|
||||
// ✅ DO: Lazy evaluation
|
||||
this.#logger.debug('Data', () => ({
|
||||
result: this.expensive() // Only runs if debug enabled
|
||||
}));
|
||||
|
||||
// ❌ DON'T: Eager evaluation
|
||||
this.#logger.debug('Data', {
|
||||
result: this.expensive() // Always runs
|
||||
});
|
||||
|
||||
// ✅ DO: Log aggregates
|
||||
this.#logger.info('Batch done', () => ({ count: items.length }));
|
||||
|
||||
// ❌ DON'T: Log in loops
|
||||
for (const item of items) {
|
||||
this.#logger.debug('Item', { item }); // Performance hit
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```typescript
|
||||
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
|
||||
import { LoggingService } from '@isa/core/logging';
|
||||
|
||||
describe('MyComponent', () => {
|
||||
const createComponent = createComponentFactory({
|
||||
component: MyComponent,
|
||||
mocks: [LoggingService]
|
||||
});
|
||||
|
||||
it('logs error', () => {
|
||||
const spectator = createComponent();
|
||||
const logger = spectator.inject(LoggingService);
|
||||
|
||||
spectator.component.operation();
|
||||
|
||||
expect(logger.error).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Custom Sink
|
||||
|
||||
```typescript
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Sink, LogLevel, LoggerContext } from '@isa/core/logging';
|
||||
|
||||
@Injectable()
|
||||
export class CustomSink implements Sink {
|
||||
log(level: LogLevel, message: string, context?: LoggerContext, error?: Error): void {
|
||||
// Implementation
|
||||
}
|
||||
}
|
||||
|
||||
// Register
|
||||
provideLogging(withSink(CustomSink))
|
||||
```
|
||||
|
||||
## Sink Function (with DI)
|
||||
|
||||
```typescript
|
||||
import { inject } from '@angular/core';
|
||||
import { SinkFn, LogLevel } from '@isa/core/logging';
|
||||
|
||||
export const remoteSink: SinkFn = () => {
|
||||
const http = inject(HttpClient);
|
||||
|
||||
return (level, message, context, error) => {
|
||||
if (level === LogLevel.Error) {
|
||||
http.post('/api/logs', { level, message, context, error }).subscribe();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Register
|
||||
provideLogging(withSinkFn(remoteSink))
|
||||
```
|
||||
|
||||
## Common Imports
|
||||
|
||||
```typescript
|
||||
// Main imports
|
||||
import { logger, provideLoggerContext } from '@isa/core/logging';
|
||||
|
||||
// Configuration imports
|
||||
import {
|
||||
provideLogging,
|
||||
withLogLevel,
|
||||
withSink,
|
||||
withContext,
|
||||
LogLevel,
|
||||
ConsoleLogSink
|
||||
} from '@isa/core/logging';
|
||||
|
||||
// Type imports
|
||||
import {
|
||||
LoggerApi,
|
||||
Sink,
|
||||
SinkFn,
|
||||
LoggerContext
|
||||
} from '@isa/core/logging';
|
||||
```
|
||||
@@ -1,350 +1,350 @@
|
||||
# Logging Examples
|
||||
|
||||
Concise real-world examples of logging patterns.
|
||||
|
||||
## 1. Component with Observable
|
||||
|
||||
```typescript
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { logger } from '@isa/core/logging';
|
||||
|
||||
@Component({
|
||||
selector: 'app-product-list',
|
||||
standalone: true,
|
||||
})
|
||||
export class ProductListComponent implements OnInit {
|
||||
#logger = logger({ component: 'ProductListComponent' });
|
||||
|
||||
constructor(private productService: ProductService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.#logger.info('Component initialized');
|
||||
this.loadProducts();
|
||||
}
|
||||
|
||||
private loadProducts(): void {
|
||||
this.productService.getProducts().subscribe({
|
||||
next: (products) => {
|
||||
this.#logger.info('Products loaded', () => ({ count: products.length }));
|
||||
},
|
||||
error: (error) => {
|
||||
this.#logger.error('Failed to load products', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Service with HTTP
|
||||
|
||||
```typescript
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { logger } from '@isa/core/logging';
|
||||
import { catchError, tap } from 'rxjs/operators';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class OrderService {
|
||||
private http = inject(HttpClient);
|
||||
#logger = logger({ service: 'OrderService' });
|
||||
|
||||
getOrder(id: string): Observable<Order> {
|
||||
this.#logger.debug('Fetching order', { id });
|
||||
|
||||
return this.http.get<Order>(`/api/orders/${id}`).pipe(
|
||||
tap((order) => this.#logger.info('Order fetched', () => ({
|
||||
id,
|
||||
status: order.status
|
||||
}))),
|
||||
catchError((error) => {
|
||||
this.#logger.error('Fetch failed', error, () => ({ id, status: error.status }));
|
||||
throw error;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Hierarchical Context
|
||||
|
||||
```typescript
|
||||
import { Component } from '@angular/core';
|
||||
import { logger, provideLoggerContext } from '@isa/core/logging';
|
||||
|
||||
@Component({
|
||||
selector: 'oms-return-process',
|
||||
standalone: true,
|
||||
providers: [
|
||||
provideLoggerContext({ feature: 'returns', module: 'oms' })
|
||||
],
|
||||
})
|
||||
export class ReturnProcessComponent {
|
||||
#logger = logger(() => ({
|
||||
processId: this.currentProcessId,
|
||||
step: this.currentStep
|
||||
}));
|
||||
|
||||
private currentProcessId = crypto.randomUUID();
|
||||
private currentStep = 1;
|
||||
|
||||
startProcess(orderId: string): void {
|
||||
// Logs include: feature, module, processId, step, orderId
|
||||
this.#logger.info('Process started', { orderId });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4. NgRx Effect
|
||||
|
||||
```typescript
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||
import { logger } from '@isa/core/logging';
|
||||
import { map, catchError, tap } from 'rxjs/operators';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export class OrdersEffects {
|
||||
#logger = logger({ effect: 'OrdersEffects' });
|
||||
|
||||
loadOrders$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(OrdersActions.loadOrders),
|
||||
tap((action) => this.#logger.debug('Loading orders', () => ({
|
||||
page: action.page
|
||||
}))),
|
||||
mergeMap((action) =>
|
||||
this.orderService.getOrders(action.filters).pipe(
|
||||
map((orders) => {
|
||||
this.#logger.info('Orders loaded', () => ({ count: orders.length }));
|
||||
return OrdersActions.loadOrdersSuccess({ orders });
|
||||
}),
|
||||
catchError((error) => {
|
||||
this.#logger.error('Load failed', error);
|
||||
return of(OrdersActions.loadOrdersFailure({ error }));
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
constructor(
|
||||
private actions$: Actions,
|
||||
private orderService: OrderService
|
||||
) {}
|
||||
}
|
||||
```
|
||||
|
||||
## 5. Guard with Authorization
|
||||
|
||||
```typescript
|
||||
import { inject } from '@angular/core';
|
||||
import { CanActivateFn, Router } from '@angular/router';
|
||||
import { logger } from '@isa/core/logging';
|
||||
|
||||
export const authGuard: CanActivateFn = (route, state) => {
|
||||
const authService = inject(AuthService);
|
||||
const router = inject(Router);
|
||||
const log = logger({ guard: 'AuthGuard' });
|
||||
|
||||
if (authService.isAuthenticated()) {
|
||||
log.debug('Access granted', () => ({ route: state.url }));
|
||||
return true;
|
||||
}
|
||||
|
||||
log.warn('Access denied', () => ({
|
||||
attemptedRoute: state.url,
|
||||
redirectTo: '/login'
|
||||
}));
|
||||
return router.createUrlTree(['/login']);
|
||||
};
|
||||
```
|
||||
|
||||
## 6. HTTP Interceptor
|
||||
|
||||
```typescript
|
||||
import { HttpInterceptorFn } from '@angular/common/http';
|
||||
import { inject } from '@angular/core';
|
||||
import { tap, catchError } from 'rxjs/operators';
|
||||
import { LoggingService } from '@isa/core/logging';
|
||||
|
||||
export const loggingInterceptor: HttpInterceptorFn = (req, next) => {
|
||||
const loggingService = inject(LoggingService);
|
||||
const startTime = performance.now();
|
||||
|
||||
loggingService.debug('HTTP Request', () => ({
|
||||
method: req.method,
|
||||
url: req.url
|
||||
}));
|
||||
|
||||
return next(req).pipe(
|
||||
tap((event) => {
|
||||
if (event.type === HttpEventType.Response) {
|
||||
loggingService.info('HTTP Response', () => ({
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
status: event.status,
|
||||
duration: `${(performance.now() - startTime).toFixed(2)}ms`
|
||||
}));
|
||||
}
|
||||
}),
|
||||
catchError((error) => {
|
||||
loggingService.error('HTTP Error', error, () => ({
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
status: error.status
|
||||
}));
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## 7. Form Validation
|
||||
|
||||
```typescript
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { logger } from '@isa/core/logging';
|
||||
|
||||
@Component({
|
||||
selector: 'shared-user-form',
|
||||
standalone: true,
|
||||
})
|
||||
export class UserFormComponent implements OnInit {
|
||||
#logger = logger({ component: 'UserFormComponent' });
|
||||
form!: FormGroup;
|
||||
|
||||
constructor(private fb: FormBuilder) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.form = this.fb.group({
|
||||
name: ['', Validators.required],
|
||||
email: ['', [Validators.required, Validators.email]]
|
||||
});
|
||||
}
|
||||
|
||||
onSubmit(): void {
|
||||
if (this.form.invalid) {
|
||||
this.#logger.warn('Invalid form submission', () => ({
|
||||
errors: this.getFormErrors()
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
this.#logger.info('Form submitted');
|
||||
}
|
||||
|
||||
private getFormErrors(): Record<string, unknown> {
|
||||
const errors: Record<string, unknown> = {};
|
||||
Object.keys(this.form.controls).forEach((key) => {
|
||||
const control = this.form.get(key);
|
||||
if (control?.errors) errors[key] = control.errors;
|
||||
});
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 8. Async Progress Tracking
|
||||
|
||||
```typescript
|
||||
import { Injectable } from '@angular/core';
|
||||
import { logger } from '@isa/core/logging';
|
||||
import { Observable } from 'rxjs';
|
||||
import { tap } from 'rxjs/operators';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ImportService {
|
||||
#logger = logger({ service: 'ImportService' });
|
||||
|
||||
importData(file: File): Observable<number> {
|
||||
const importId = crypto.randomUUID();
|
||||
|
||||
this.#logger.info('Import started', () => ({
|
||||
importId,
|
||||
fileName: file.name,
|
||||
fileSize: file.size
|
||||
}));
|
||||
|
||||
return this.processImport(file).pipe(
|
||||
tap((progress) => {
|
||||
if (progress % 25 === 0) {
|
||||
this.#logger.debug('Import progress', () => ({
|
||||
importId,
|
||||
progress: `${progress}%`
|
||||
}));
|
||||
}
|
||||
}),
|
||||
tap({
|
||||
complete: () => this.#logger.info('Import completed', { importId }),
|
||||
error: (error) => this.#logger.error('Import failed', error, { importId })
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private processImport(file: File): Observable<number> {
|
||||
// Implementation
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 9. Global Error Handler
|
||||
|
||||
```typescript
|
||||
import { Injectable, ErrorHandler } from '@angular/core';
|
||||
import { logger } from '@isa/core/logging';
|
||||
|
||||
@Injectable()
|
||||
export class GlobalErrorHandler implements ErrorHandler {
|
||||
#logger = logger({ handler: 'GlobalErrorHandler' });
|
||||
|
||||
handleError(error: Error): void {
|
||||
this.#logger.error('Uncaught error', error, () => ({
|
||||
url: window.location.href,
|
||||
userAgent: navigator.userAgent,
|
||||
timestamp: new Date().toISOString()
|
||||
}));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 10. WebSocket Component
|
||||
|
||||
```typescript
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { logger } from '@isa/core/logging';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'oms-live-orders',
|
||||
standalone: true,
|
||||
})
|
||||
export class LiveOrdersComponent implements OnInit, OnDestroy {
|
||||
#logger = logger({ component: 'LiveOrdersComponent' });
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(private wsService: WebSocketService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.#logger.info('Connecting to WebSocket');
|
||||
|
||||
this.wsService.connect('orders').pipe(
|
||||
takeUntil(this.destroy$)
|
||||
).subscribe({
|
||||
next: (msg) => this.#logger.debug('Message received', () => ({
|
||||
type: msg.type,
|
||||
orderId: msg.orderId
|
||||
})),
|
||||
error: (error) => this.#logger.error('WebSocket error', error),
|
||||
complete: () => this.#logger.info('WebSocket closed')
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.#logger.debug('Component destroyed');
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
||||
```
|
||||
# Logging Examples
|
||||
|
||||
Concise real-world examples of logging patterns.
|
||||
|
||||
## 1. Component with Observable
|
||||
|
||||
```typescript
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { logger } from '@isa/core/logging';
|
||||
|
||||
@Component({
|
||||
selector: 'app-product-list',
|
||||
standalone: true,
|
||||
})
|
||||
export class ProductListComponent implements OnInit {
|
||||
#logger = logger({ component: 'ProductListComponent' });
|
||||
|
||||
constructor(private productService: ProductService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.#logger.info('Component initialized');
|
||||
this.loadProducts();
|
||||
}
|
||||
|
||||
private loadProducts(): void {
|
||||
this.productService.getProducts().subscribe({
|
||||
next: (products) => {
|
||||
this.#logger.info('Products loaded', () => ({ count: products.length }));
|
||||
},
|
||||
error: (error) => {
|
||||
this.#logger.error('Failed to load products', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Service with HTTP
|
||||
|
||||
```typescript
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { logger } from '@isa/core/logging';
|
||||
import { catchError, tap } from 'rxjs/operators';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class OrderService {
|
||||
private http = inject(HttpClient);
|
||||
#logger = logger({ service: 'OrderService' });
|
||||
|
||||
getOrder(id: string): Observable<Order> {
|
||||
this.#logger.debug('Fetching order', { id });
|
||||
|
||||
return this.http.get<Order>(`/api/orders/${id}`).pipe(
|
||||
tap((order) => this.#logger.info('Order fetched', () => ({
|
||||
id,
|
||||
status: order.status
|
||||
}))),
|
||||
catchError((error) => {
|
||||
this.#logger.error('Fetch failed', error, () => ({ id, status: error.status }));
|
||||
throw error;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Hierarchical Context
|
||||
|
||||
```typescript
|
||||
import { Component } from '@angular/core';
|
||||
import { logger, provideLoggerContext } from '@isa/core/logging';
|
||||
|
||||
@Component({
|
||||
selector: 'oms-return-process',
|
||||
standalone: true,
|
||||
providers: [
|
||||
provideLoggerContext({ feature: 'returns', module: 'oms' })
|
||||
],
|
||||
})
|
||||
export class ReturnProcessComponent {
|
||||
#logger = logger(() => ({
|
||||
processId: this.currentProcessId,
|
||||
step: this.currentStep
|
||||
}));
|
||||
|
||||
private currentProcessId = crypto.randomUUID();
|
||||
private currentStep = 1;
|
||||
|
||||
startProcess(orderId: string): void {
|
||||
// Logs include: feature, module, processId, step, orderId
|
||||
this.#logger.info('Process started', { orderId });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4. NgRx Effect
|
||||
|
||||
```typescript
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||
import { logger } from '@isa/core/logging';
|
||||
import { map, catchError, tap } from 'rxjs/operators';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export class OrdersEffects {
|
||||
#logger = logger({ effect: 'OrdersEffects' });
|
||||
|
||||
loadOrders$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(OrdersActions.loadOrders),
|
||||
tap((action) => this.#logger.debug('Loading orders', () => ({
|
||||
page: action.page
|
||||
}))),
|
||||
mergeMap((action) =>
|
||||
this.orderService.getOrders(action.filters).pipe(
|
||||
map((orders) => {
|
||||
this.#logger.info('Orders loaded', () => ({ count: orders.length }));
|
||||
return OrdersActions.loadOrdersSuccess({ orders });
|
||||
}),
|
||||
catchError((error) => {
|
||||
this.#logger.error('Load failed', error);
|
||||
return of(OrdersActions.loadOrdersFailure({ error }));
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
constructor(
|
||||
private actions$: Actions,
|
||||
private orderService: OrderService
|
||||
) {}
|
||||
}
|
||||
```
|
||||
|
||||
## 5. Guard with Authorization
|
||||
|
||||
```typescript
|
||||
import { inject } from '@angular/core';
|
||||
import { CanActivateFn, Router } from '@angular/router';
|
||||
import { logger } from '@isa/core/logging';
|
||||
|
||||
export const authGuard: CanActivateFn = (route, state) => {
|
||||
const authService = inject(AuthService);
|
||||
const router = inject(Router);
|
||||
const log = logger({ guard: 'AuthGuard' });
|
||||
|
||||
if (authService.isAuthenticated()) {
|
||||
log.debug('Access granted', () => ({ route: state.url }));
|
||||
return true;
|
||||
}
|
||||
|
||||
log.warn('Access denied', () => ({
|
||||
attemptedRoute: state.url,
|
||||
redirectTo: '/login'
|
||||
}));
|
||||
return router.createUrlTree(['/login']);
|
||||
};
|
||||
```
|
||||
|
||||
## 6. HTTP Interceptor
|
||||
|
||||
```typescript
|
||||
import { HttpInterceptorFn } from '@angular/common/http';
|
||||
import { inject } from '@angular/core';
|
||||
import { tap, catchError } from 'rxjs/operators';
|
||||
import { LoggingService } from '@isa/core/logging';
|
||||
|
||||
export const loggingInterceptor: HttpInterceptorFn = (req, next) => {
|
||||
const loggingService = inject(LoggingService);
|
||||
const startTime = performance.now();
|
||||
|
||||
loggingService.debug('HTTP Request', () => ({
|
||||
method: req.method,
|
||||
url: req.url
|
||||
}));
|
||||
|
||||
return next(req).pipe(
|
||||
tap((event) => {
|
||||
if (event.type === HttpEventType.Response) {
|
||||
loggingService.info('HTTP Response', () => ({
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
status: event.status,
|
||||
duration: `${(performance.now() - startTime).toFixed(2)}ms`
|
||||
}));
|
||||
}
|
||||
}),
|
||||
catchError((error) => {
|
||||
loggingService.error('HTTP Error', error, () => ({
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
status: error.status
|
||||
}));
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## 7. Form Validation
|
||||
|
||||
```typescript
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { logger } from '@isa/core/logging';
|
||||
|
||||
@Component({
|
||||
selector: 'shared-user-form',
|
||||
standalone: true,
|
||||
})
|
||||
export class UserFormComponent implements OnInit {
|
||||
#logger = logger({ component: 'UserFormComponent' });
|
||||
form!: FormGroup;
|
||||
|
||||
constructor(private fb: FormBuilder) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.form = this.fb.group({
|
||||
name: ['', Validators.required],
|
||||
email: ['', [Validators.required, Validators.email]]
|
||||
});
|
||||
}
|
||||
|
||||
onSubmit(): void {
|
||||
if (this.form.invalid) {
|
||||
this.#logger.warn('Invalid form submission', () => ({
|
||||
errors: this.getFormErrors()
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
this.#logger.info('Form submitted');
|
||||
}
|
||||
|
||||
private getFormErrors(): Record<string, unknown> {
|
||||
const errors: Record<string, unknown> = {};
|
||||
Object.keys(this.form.controls).forEach((key) => {
|
||||
const control = this.form.get(key);
|
||||
if (control?.errors) errors[key] = control.errors;
|
||||
});
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 8. Async Progress Tracking
|
||||
|
||||
```typescript
|
||||
import { Injectable } from '@angular/core';
|
||||
import { logger } from '@isa/core/logging';
|
||||
import { Observable } from 'rxjs';
|
||||
import { tap } from 'rxjs/operators';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ImportService {
|
||||
#logger = logger({ service: 'ImportService' });
|
||||
|
||||
importData(file: File): Observable<number> {
|
||||
const importId = crypto.randomUUID();
|
||||
|
||||
this.#logger.info('Import started', () => ({
|
||||
importId,
|
||||
fileName: file.name,
|
||||
fileSize: file.size
|
||||
}));
|
||||
|
||||
return this.processImport(file).pipe(
|
||||
tap((progress) => {
|
||||
if (progress % 25 === 0) {
|
||||
this.#logger.debug('Import progress', () => ({
|
||||
importId,
|
||||
progress: `${progress}%`
|
||||
}));
|
||||
}
|
||||
}),
|
||||
tap({
|
||||
complete: () => this.#logger.info('Import completed', { importId }),
|
||||
error: (error) => this.#logger.error('Import failed', error, { importId })
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private processImport(file: File): Observable<number> {
|
||||
// Implementation
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 9. Global Error Handler
|
||||
|
||||
```typescript
|
||||
import { Injectable, ErrorHandler } from '@angular/core';
|
||||
import { logger } from '@isa/core/logging';
|
||||
|
||||
@Injectable()
|
||||
export class GlobalErrorHandler implements ErrorHandler {
|
||||
#logger = logger({ handler: 'GlobalErrorHandler' });
|
||||
|
||||
handleError(error: Error): void {
|
||||
this.#logger.error('Uncaught error', error, () => ({
|
||||
url: window.location.href,
|
||||
userAgent: navigator.userAgent,
|
||||
timestamp: new Date().toISOString()
|
||||
}));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 10. WebSocket Component
|
||||
|
||||
```typescript
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { logger } from '@isa/core/logging';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'oms-live-orders',
|
||||
standalone: true,
|
||||
})
|
||||
export class LiveOrdersComponent implements OnInit, OnDestroy {
|
||||
#logger = logger({ component: 'LiveOrdersComponent' });
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(private wsService: WebSocketService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.#logger.info('Connecting to WebSocket');
|
||||
|
||||
this.wsService.connect('orders').pipe(
|
||||
takeUntil(this.destroy$)
|
||||
).subscribe({
|
||||
next: (msg) => this.#logger.debug('Message received', () => ({
|
||||
type: msg.type,
|
||||
orderId: msg.orderId
|
||||
})),
|
||||
error: (error) => this.#logger.error('WebSocket error', error),
|
||||
complete: () => this.#logger.info('WebSocket closed')
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.#logger.debug('Component destroyed');
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,235 +1,235 @@
|
||||
# Logging Troubleshooting
|
||||
|
||||
## 1. Logs Not Appearing
|
||||
|
||||
**Problem:** Logger called but nothing in console.
|
||||
|
||||
**Solutions:**
|
||||
```typescript
|
||||
// Check log level
|
||||
provideLogging(
|
||||
withLogLevel(isDevMode() ? LogLevel.Debug : LogLevel.Warn)
|
||||
)
|
||||
|
||||
// Add sink
|
||||
provideLogging(
|
||||
withLogLevel(LogLevel.Debug),
|
||||
withSink(ConsoleLogSink) // Required!
|
||||
)
|
||||
|
||||
// Verify configuration in app.config.ts
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideLogging(...) // Must be present
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
## 2. NullInjectorError
|
||||
|
||||
**Error:** `NullInjectorError: No provider for LoggingService!`
|
||||
|
||||
**Solution:**
|
||||
```typescript
|
||||
// app.config.ts
|
||||
import { provideLogging, withLogLevel, withSink,
|
||||
LogLevel, ConsoleLogSink } from '@isa/core/logging';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideLogging(
|
||||
withLogLevel(LogLevel.Debug),
|
||||
withSink(ConsoleLogSink)
|
||||
)
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
## 3. Context Not Showing
|
||||
|
||||
**Problem:** Context passed but doesn't appear.
|
||||
|
||||
**Check:**
|
||||
```typescript
|
||||
// ✅ Both work:
|
||||
this.#logger.info('Message', () => ({ id: '123' })); // Function
|
||||
this.#logger.info('Message', { id: '123' }); // Object
|
||||
|
||||
// ❌ Common mistake:
|
||||
const ctx = { id: '123' };
|
||||
this.#logger.info('Message', ctx); // Actually works!
|
||||
|
||||
// Verify hierarchical merge:
|
||||
// Global → Component → Instance → Message
|
||||
```
|
||||
|
||||
## 4. Performance Issues
|
||||
|
||||
**Problem:** Slow when debug logging enabled.
|
||||
|
||||
**Solutions:**
|
||||
```typescript
|
||||
// ✅ Use lazy evaluation
|
||||
this.#logger.debug('Data', () => ({
|
||||
expensive: this.compute() // Only if debug enabled
|
||||
}));
|
||||
|
||||
// ✅ Reduce log frequency
|
||||
this.#logger.debug('Batch', () => ({
|
||||
count: items.length // Not each item
|
||||
}));
|
||||
|
||||
// ✅ Increase production level
|
||||
provideLogging(
|
||||
withLogLevel(isDevMode() ? LogLevel.Debug : LogLevel.Warn)
|
||||
)
|
||||
```
|
||||
|
||||
## 5. Error Object Not Logged
|
||||
|
||||
**Problem:** Error shows as `[object Object]`.
|
||||
|
||||
**Solution:**
|
||||
```typescript
|
||||
// ❌ Wrong
|
||||
this.#logger.error('Failed', { error }); // Don't wrap in object
|
||||
|
||||
// ✅ Correct
|
||||
this.#logger.error('Failed', error as Error, () => ({
|
||||
additionalContext: 'value'
|
||||
}));
|
||||
```
|
||||
|
||||
## 6. TypeScript Errors
|
||||
|
||||
**Error:** `Type 'X' is not assignable to 'MaybeLoggerContextFn'`
|
||||
|
||||
**Solution:**
|
||||
```typescript
|
||||
// ❌ Wrong type
|
||||
this.#logger.info('Message', 'string'); // Invalid
|
||||
|
||||
// ✅ Correct types
|
||||
this.#logger.info('Message', { key: 'value' });
|
||||
this.#logger.info('Message', () => ({ key: 'value' }));
|
||||
```
|
||||
|
||||
## 7. Logs in Tests
|
||||
|
||||
**Problem:** Test output cluttered with logs.
|
||||
|
||||
**Solutions:**
|
||||
```typescript
|
||||
// Mock logging service
|
||||
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
|
||||
import { LoggingService } from '@isa/core/logging';
|
||||
|
||||
const createComponent = createComponentFactory({
|
||||
component: MyComponent,
|
||||
mocks: [LoggingService] // Mocks all log methods
|
||||
});
|
||||
|
||||
// Or disable in tests
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
provideLogging(withLogLevel(LogLevel.Off))
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
## 8. Undefined Property Error
|
||||
|
||||
**Error:** `Cannot read property 'X' of undefined`
|
||||
|
||||
**Problem:** Accessing uninitialized property in logger context.
|
||||
|
||||
**Solutions:**
|
||||
```typescript
|
||||
// ❌ Problem
|
||||
#logger = logger(() => ({
|
||||
userId: this.userService.currentUserId // May be undefined
|
||||
}));
|
||||
|
||||
// ✅ Solution 1: Optional chaining
|
||||
#logger = logger(() => ({
|
||||
userId: this.userService?.currentUserId ?? 'unknown'
|
||||
}));
|
||||
|
||||
// ✅ Solution 2: Delay access
|
||||
ngOnInit() {
|
||||
this.#logger.info('Init', () => ({
|
||||
userId: this.userService.currentUserId // Safe here
|
||||
}));
|
||||
}
|
||||
```
|
||||
|
||||
## 9. Circular Dependency
|
||||
|
||||
**Error:** `NG0200: Circular dependency in DI detected`
|
||||
|
||||
**Cause:** Service A ← → Service B both inject LoggingService.
|
||||
|
||||
**Solution:**
|
||||
```typescript
|
||||
// ❌ Creates circular dependency
|
||||
constructor(private loggingService: LoggingService) {}
|
||||
|
||||
// ✅ Use factory (no circular dependency)
|
||||
#logger = logger({ service: 'MyService' });
|
||||
```
|
||||
|
||||
## 10. Custom Sink Not Working
|
||||
|
||||
**Problem:** Sink registered but never called.
|
||||
|
||||
**Solutions:**
|
||||
```typescript
|
||||
// ✅ Correct registration
|
||||
provideLogging(
|
||||
withSink(MySink) // Add to config
|
||||
)
|
||||
|
||||
// ✅ Correct signature
|
||||
export class MySink implements Sink {
|
||||
log(
|
||||
level: LogLevel,
|
||||
message: string,
|
||||
context?: LoggerContext,
|
||||
error?: Error
|
||||
): void {
|
||||
// Implementation
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Sink function must return function
|
||||
export const mySinkFn: SinkFn = () => {
|
||||
const http = inject(HttpClient);
|
||||
return (level, message, context, error) => {
|
||||
// Implementation
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
## Quick Diagnostics
|
||||
|
||||
```typescript
|
||||
// Enable all logs temporarily
|
||||
provideLogging(withLogLevel(LogLevel.Trace))
|
||||
|
||||
// Check imports
|
||||
import { logger } from '@isa/core/logging'; // ✅ Correct
|
||||
import { logger } from '@isa/core/logging/src/lib/logger.factory'; // ❌ Wrong
|
||||
|
||||
// Verify console filters in browser DevTools
|
||||
// Ensure Info, Debug, Warnings are enabled
|
||||
```
|
||||
|
||||
## Common Error Messages
|
||||
|
||||
| Error | Cause | Fix |
|
||||
|-------|-------|-----|
|
||||
| `NullInjectorError: LoggingService` | Missing config | Add `provideLogging()` |
|
||||
| `Type 'X' not assignable` | Wrong context type | Use object or function |
|
||||
| `Cannot read property 'X'` | Undefined property | Use optional chaining |
|
||||
| `Circular dependency` | Service injection | Use `logger()` factory |
|
||||
| Stack overflow | Infinite loop in context | Don't call logger in context |
|
||||
# Logging Troubleshooting
|
||||
|
||||
## 1. Logs Not Appearing
|
||||
|
||||
**Problem:** Logger called but nothing in console.
|
||||
|
||||
**Solutions:**
|
||||
```typescript
|
||||
// Check log level
|
||||
provideLogging(
|
||||
withLogLevel(isDevMode() ? LogLevel.Debug : LogLevel.Warn)
|
||||
)
|
||||
|
||||
// Add sink
|
||||
provideLogging(
|
||||
withLogLevel(LogLevel.Debug),
|
||||
withSink(ConsoleLogSink) // Required!
|
||||
)
|
||||
|
||||
// Verify configuration in app.config.ts
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideLogging(...) // Must be present
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
## 2. NullInjectorError
|
||||
|
||||
**Error:** `NullInjectorError: No provider for LoggingService!`
|
||||
|
||||
**Solution:**
|
||||
```typescript
|
||||
// app.config.ts
|
||||
import { provideLogging, withLogLevel, withSink,
|
||||
LogLevel, ConsoleLogSink } from '@isa/core/logging';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideLogging(
|
||||
withLogLevel(LogLevel.Debug),
|
||||
withSink(ConsoleLogSink)
|
||||
)
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
## 3. Context Not Showing
|
||||
|
||||
**Problem:** Context passed but doesn't appear.
|
||||
|
||||
**Check:**
|
||||
```typescript
|
||||
// ✅ Both work:
|
||||
this.#logger.info('Message', () => ({ id: '123' })); // Function
|
||||
this.#logger.info('Message', { id: '123' }); // Object
|
||||
|
||||
// ❌ Common mistake:
|
||||
const ctx = { id: '123' };
|
||||
this.#logger.info('Message', ctx); // Actually works!
|
||||
|
||||
// Verify hierarchical merge:
|
||||
// Global → Component → Instance → Message
|
||||
```
|
||||
|
||||
## 4. Performance Issues
|
||||
|
||||
**Problem:** Slow when debug logging enabled.
|
||||
|
||||
**Solutions:**
|
||||
```typescript
|
||||
// ✅ Use lazy evaluation
|
||||
this.#logger.debug('Data', () => ({
|
||||
expensive: this.compute() // Only if debug enabled
|
||||
}));
|
||||
|
||||
// ✅ Reduce log frequency
|
||||
this.#logger.debug('Batch', () => ({
|
||||
count: items.length // Not each item
|
||||
}));
|
||||
|
||||
// ✅ Increase production level
|
||||
provideLogging(
|
||||
withLogLevel(isDevMode() ? LogLevel.Debug : LogLevel.Warn)
|
||||
)
|
||||
```
|
||||
|
||||
## 5. Error Object Not Logged
|
||||
|
||||
**Problem:** Error shows as `[object Object]`.
|
||||
|
||||
**Solution:**
|
||||
```typescript
|
||||
// ❌ Wrong
|
||||
this.#logger.error('Failed', { error }); // Don't wrap in object
|
||||
|
||||
// ✅ Correct
|
||||
this.#logger.error('Failed', error as Error, () => ({
|
||||
additionalContext: 'value'
|
||||
}));
|
||||
```
|
||||
|
||||
## 6. TypeScript Errors
|
||||
|
||||
**Error:** `Type 'X' is not assignable to 'MaybeLoggerContextFn'`
|
||||
|
||||
**Solution:**
|
||||
```typescript
|
||||
// ❌ Wrong type
|
||||
this.#logger.info('Message', 'string'); // Invalid
|
||||
|
||||
// ✅ Correct types
|
||||
this.#logger.info('Message', { key: 'value' });
|
||||
this.#logger.info('Message', () => ({ key: 'value' }));
|
||||
```
|
||||
|
||||
## 7. Logs in Tests
|
||||
|
||||
**Problem:** Test output cluttered with logs.
|
||||
|
||||
**Solutions:**
|
||||
```typescript
|
||||
// Mock logging service
|
||||
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
|
||||
import { LoggingService } from '@isa/core/logging';
|
||||
|
||||
const createComponent = createComponentFactory({
|
||||
component: MyComponent,
|
||||
mocks: [LoggingService] // Mocks all log methods
|
||||
});
|
||||
|
||||
// Or disable in tests
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
provideLogging(withLogLevel(LogLevel.Off))
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
## 8. Undefined Property Error
|
||||
|
||||
**Error:** `Cannot read property 'X' of undefined`
|
||||
|
||||
**Problem:** Accessing uninitialized property in logger context.
|
||||
|
||||
**Solutions:**
|
||||
```typescript
|
||||
// ❌ Problem
|
||||
#logger = logger(() => ({
|
||||
userId: this.userService.currentUserId // May be undefined
|
||||
}));
|
||||
|
||||
// ✅ Solution 1: Optional chaining
|
||||
#logger = logger(() => ({
|
||||
userId: this.userService?.currentUserId ?? 'unknown'
|
||||
}));
|
||||
|
||||
// ✅ Solution 2: Delay access
|
||||
ngOnInit() {
|
||||
this.#logger.info('Init', () => ({
|
||||
userId: this.userService.currentUserId // Safe here
|
||||
}));
|
||||
}
|
||||
```
|
||||
|
||||
## 9. Circular Dependency
|
||||
|
||||
**Error:** `NG0200: Circular dependency in DI detected`
|
||||
|
||||
**Cause:** Service A ← → Service B both inject LoggingService.
|
||||
|
||||
**Solution:**
|
||||
```typescript
|
||||
// ❌ Creates circular dependency
|
||||
constructor(private loggingService: LoggingService) {}
|
||||
|
||||
// ✅ Use factory (no circular dependency)
|
||||
#logger = logger({ service: 'MyService' });
|
||||
```
|
||||
|
||||
## 10. Custom Sink Not Working
|
||||
|
||||
**Problem:** Sink registered but never called.
|
||||
|
||||
**Solutions:**
|
||||
```typescript
|
||||
// ✅ Correct registration
|
||||
provideLogging(
|
||||
withSink(MySink) // Add to config
|
||||
)
|
||||
|
||||
// ✅ Correct signature
|
||||
export class MySink implements Sink {
|
||||
log(
|
||||
level: LogLevel,
|
||||
message: string,
|
||||
context?: LoggerContext,
|
||||
error?: Error
|
||||
): void {
|
||||
// Implementation
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Sink function must return function
|
||||
export const mySinkFn: SinkFn = () => {
|
||||
const http = inject(HttpClient);
|
||||
return (level, message, context, error) => {
|
||||
// Implementation
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
## Quick Diagnostics
|
||||
|
||||
```typescript
|
||||
// Enable all logs temporarily
|
||||
provideLogging(withLogLevel(LogLevel.Trace))
|
||||
|
||||
// Check imports
|
||||
import { logger } from '@isa/core/logging'; // ✅ Correct
|
||||
import { logger } from '@isa/core/logging/src/lib/logger.factory'; // ❌ Wrong
|
||||
|
||||
// Verify console filters in browser DevTools
|
||||
// Ensure Info, Debug, Warnings are enabled
|
||||
```
|
||||
|
||||
## Common Error Messages
|
||||
|
||||
| Error | Cause | Fix |
|
||||
|-------|-------|-----|
|
||||
| `NullInjectorError: LoggingService` | Missing config | Add `provideLogging()` |
|
||||
| `Type 'X' not assignable` | Wrong context type | Use object or function |
|
||||
| `Cannot read property 'X'` | Undefined property | Use optional chaining |
|
||||
| `Circular dependency` | Service injection | Use `logger()` factory |
|
||||
| Stack overflow | Infinite loop in context | Don't call logger in context |
|
||||
@@ -9,29 +9,6 @@ description: This skill should be used when working with Tailwind CSS styling in
|
||||
|
||||
Assist with applying the ISA-specific Tailwind CSS design system throughout the ISA-Frontend Angular monorepo. This skill provides comprehensive knowledge of custom utilities, color palettes, typography classes, button variants, and layout patterns specific to this project.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Invoke this skill when:
|
||||
- **After** checking `libs/ui/**` for existing components (always check first!)
|
||||
- Styling layout and spacing for components
|
||||
- Choosing appropriate color values for custom elements
|
||||
- Applying typography classes to text content
|
||||
- Determining spacing, layout, or responsive breakpoints
|
||||
- Customizing or extending existing UI components
|
||||
- Ensuring design system consistency
|
||||
- Questions about which Tailwind utility classes are available
|
||||
|
||||
**Important**: This skill provides Tailwind utilities. Always prefer using components from `@isa/ui/*` libraries before applying custom Tailwind styles.
|
||||
|
||||
**Works together with:**
|
||||
- **[template-standards](../template-standards/SKILL.md)** - Angular template syntax, E2E testing attributes, and ARIA accessibility
|
||||
- **[logging](../logging/SKILL.md)** - MANDATORY logging in all Angular files
|
||||
|
||||
When building Angular components, these skills work together:
|
||||
1. Use **template-standards** for Angular syntax, `data-*`, and ARIA attributes
|
||||
2. Use **tailwind** (this skill) for styling with the ISA design system
|
||||
3. Use **logging** for all component/service logging
|
||||
|
||||
## Core Design System Principles
|
||||
|
||||
### 0. Component Libraries First (Most Important)
|
||||
|
||||
@@ -7,20 +7,6 @@ description: This skill should be used when writing or reviewing Angular compone
|
||||
|
||||
Comprehensive guide for Angular templates covering modern syntax, E2E testing attributes, and ARIA accessibility.
|
||||
|
||||
## When to Use
|
||||
|
||||
- Creating or reviewing component templates
|
||||
- Refactoring legacy `*ngIf/*ngFor/*ngSwitch` to modern syntax
|
||||
- Implementing `@defer` lazy loading
|
||||
- Designing reusable components with `ng-content`
|
||||
- Adding E2E testing attributes for automated tests
|
||||
- Ensuring WCAG accessibility compliance
|
||||
- Template performance optimization
|
||||
|
||||
**Related Skills:**
|
||||
- **[tailwind](../tailwind/SKILL.md)** - ISA design system styling (colors, typography, spacing, layout)
|
||||
- **[logging](../logging/SKILL.md)** - MANDATORY logging in all Angular files using `@isa/core/logging`
|
||||
|
||||
## Overview
|
||||
|
||||
This skill combines three essential aspects of Angular template development:
|
||||
@@ -31,7 +17,9 @@ This skill combines three essential aspects of Angular template development:
|
||||
|
||||
**Every interactive element MUST include both E2E and ARIA attributes.**
|
||||
|
||||
---
|
||||
**Related Skills:**
|
||||
- **tailwind** - ISA design system styling (colors, typography, spacing, layout)
|
||||
- **logging** - MANDATORY logging in all Angular files using `@isa/core/logging`
|
||||
|
||||
## Part 1: Angular Template Syntax
|
||||
|
||||
@@ -81,6 +69,8 @@ This skill combines three essential aspects of Angular template development:
|
||||
}
|
||||
```
|
||||
|
||||
**See [control-flow-reference.md](references/control-flow-reference.md) for advanced patterns including nested loops, complex conditions, and filter strategies.**
|
||||
|
||||
### @defer Lazy Loading
|
||||
|
||||
#### Basic Usage
|
||||
@@ -97,7 +87,7 @@ This skill combines three essential aspects of Angular template development:
|
||||
}
|
||||
```
|
||||
|
||||
#### Triggers
|
||||
#### Common Triggers
|
||||
|
||||
| Trigger | Use Case |
|
||||
|---------|----------|
|
||||
@@ -111,18 +101,15 @@ This skill combines three essential aspects of Angular template development:
|
||||
**Multiple triggers:** `@defer (on interaction; on timer(5s))`
|
||||
**Prefetching:** `@defer (on interaction; prefetch on idle)`
|
||||
|
||||
#### Requirements
|
||||
#### Critical Requirements
|
||||
|
||||
- Components **MUST be standalone**
|
||||
- No `@ViewChild`/`@ContentChild` references
|
||||
- Reserve space in `@placeholder` to prevent layout shift
|
||||
- Never defer above-the-fold content (harms LCP)
|
||||
- Avoid `immediate`/`timer` during initial render (harms TTI)
|
||||
|
||||
#### Best Practices
|
||||
|
||||
- ✅ Defer below-the-fold content
|
||||
- ❌ Never defer above-the-fold (harms LCP)
|
||||
- ❌ Avoid `immediate`/`timer` during initial render (harms TTI)
|
||||
- Test with network throttling
|
||||
**See [defer-patterns.md](references/defer-patterns.md) for performance optimization, Core Web Vitals impact, bundle size reduction strategies, and real-world examples.**
|
||||
|
||||
### Content Projection
|
||||
|
||||
@@ -156,12 +143,9 @@ This skill combines three essential aspects of Angular template development:
|
||||
</ui-card>
|
||||
```
|
||||
|
||||
**Fallback content:** `<ng-content select="title">Default Title</ng-content>`
|
||||
**Aliasing:** `<h3 ngProjectAs="card-header">Title</h3>`
|
||||
**CRITICAL Constraint:** `ng-content` **always instantiates** (even if hidden). For conditional projection, use `ng-template` + `NgTemplateOutlet`.
|
||||
|
||||
#### CRITICAL Constraint
|
||||
|
||||
`ng-content` **always instantiates** (even if hidden). For conditional projection, use `ng-template` + `NgTemplateOutlet`.
|
||||
**See [projection-patterns.md](references/projection-patterns.md) for conditional projection, template-based projection, querying projected content, and modal/form field examples.**
|
||||
|
||||
### Template References
|
||||
|
||||
@@ -193,6 +177,8 @@ Groups elements without DOM footprint:
|
||||
</p>
|
||||
```
|
||||
|
||||
**See [template-reference.md](references/template-reference.md) for programmatic rendering, ViewContainerRef patterns, context scoping, and common pitfalls.**
|
||||
|
||||
### Variables
|
||||
|
||||
#### @let (Angular 18.1+)
|
||||
@@ -226,16 +212,14 @@ Groups elements without DOM footprint:
|
||||
**Class:** `[class.active]="isActive()"` or `[class]="{active: isActive()}"`
|
||||
**Style:** `[style.width.px]="width()"` or `[style]="{color: textColor()}"`
|
||||
|
||||
---
|
||||
|
||||
## Part 2: E2E Testing Attributes
|
||||
|
||||
### Purpose
|
||||
|
||||
Enable automated end-to-end testing by providing stable selectors for QA automation:
|
||||
- **`data-what`**: Semantic description of element's purpose
|
||||
- **`data-which`**: Unique identifier for specific instances
|
||||
- **`data-*`**: Additional contextual information
|
||||
- **`data-what`**: Semantic description of element's purpose (e.g., `submit-button`, `email-input`)
|
||||
- **`data-which`**: Unique identifier for specific instances (e.g., `registration-form`, `customer-123`)
|
||||
- **`data-*`**: Additional contextual information (e.g., `data-status="active"`)
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
@@ -259,9 +243,8 @@ Enable automated end-to-end testing by providing stable selectors for QA automat
|
||||
3. ✅ Ensure `data-which` is unique within the view
|
||||
4. ✅ Use Angular binding for dynamic values: `[attr.data-*]`
|
||||
5. ✅ Avoid including sensitive data in attributes
|
||||
6. ✅ Document complex attribute patterns in template comments
|
||||
|
||||
---
|
||||
**See [e2e-attributes.md](references/e2e-attributes.md) for complete patterns by element type (buttons, inputs, links, lists, tables, dialogs), dynamic attribute bindings, testing integration examples, and validation strategies.**
|
||||
|
||||
## Part 3: ARIA Accessibility Attributes
|
||||
|
||||
@@ -290,7 +273,7 @@ Ensure web applications are accessible to all users, including those using assis
|
||||
6. ✅ Keep ARIA attributes in sync with visual states
|
||||
7. ✅ Test with screen readers (NVDA, JAWS, VoiceOver)
|
||||
|
||||
---
|
||||
**See [aria-attributes.md](references/aria-attributes.md) for comprehensive role reference, property and state attributes, live regions, keyboard navigation patterns, WCAG compliance requirements, and testing strategies.**
|
||||
|
||||
## Part 4: Combined Examples
|
||||
|
||||
@@ -342,18 +325,6 @@ Ensure web applications are accessible to all users, including those using assis
|
||||
}
|
||||
```
|
||||
|
||||
### Link with All Attributes
|
||||
|
||||
```html
|
||||
<a
|
||||
[routerLink]="['/orders', orderId]"
|
||||
data-what="order-link"
|
||||
[attr.data-which]="orderId"
|
||||
[attr.aria-label]="'View order ' + orderNumber">
|
||||
View Order #{{ orderNumber }}
|
||||
</a>
|
||||
```
|
||||
|
||||
### Dialog with All Attributes
|
||||
|
||||
```html
|
||||
@@ -387,109 +358,7 @@ Ensure web applications are accessible to all users, including those using assis
|
||||
</div>
|
||||
```
|
||||
|
||||
### Complete Form Example
|
||||
|
||||
```html
|
||||
<form
|
||||
(ngSubmit)="onSubmit()"
|
||||
data-what="registration-form"
|
||||
data-which="user-signup"
|
||||
role="form"
|
||||
aria-labelledby="form-title">
|
||||
|
||||
<h2 id="form-title">User Registration</h2>
|
||||
|
||||
<div class="form-field">
|
||||
<label for="username-input">Username</label>
|
||||
<input
|
||||
id="username-input"
|
||||
type="text"
|
||||
[(ngModel)]="username"
|
||||
name="username"
|
||||
data-what="username-input"
|
||||
data-which="registration-form"
|
||||
aria-required="true"
|
||||
aria-describedby="username-hint" />
|
||||
<span id="username-hint">Must be at least 3 characters</span>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label for="email-input">Email</label>
|
||||
<input
|
||||
id="email-input"
|
||||
type="email"
|
||||
[(ngModel)]="email"
|
||||
name="email"
|
||||
data-what="email-input"
|
||||
data-which="registration-form"
|
||||
aria-required="true"
|
||||
[attr.aria-invalid]="emailError ? 'true' : null"
|
||||
aria-describedby="email-error" />
|
||||
@if (emailError) {
|
||||
<span
|
||||
id="email-error"
|
||||
role="alert"
|
||||
aria-live="polite">
|
||||
{{ emailError }}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button
|
||||
type="submit"
|
||||
data-what="submit-button"
|
||||
data-which="registration-form"
|
||||
[attr.aria-disabled]="!isValid()"
|
||||
aria-label="Submit registration form">
|
||||
Register
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
(click)="onCancel()"
|
||||
data-what="cancel-button"
|
||||
data-which="registration-form"
|
||||
aria-label="Cancel registration">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
```
|
||||
|
||||
### Conditional Rendering with Attributes
|
||||
|
||||
```typescript
|
||||
@if (isLoading()) {
|
||||
<div
|
||||
data-what="loading-spinner"
|
||||
data-which="user-profile"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
aria-label="Loading user profile">
|
||||
<mat-spinner />
|
||||
</div>
|
||||
} @else if (error()) {
|
||||
<div
|
||||
data-what="error-message"
|
||||
data-which="user-profile"
|
||||
role="alert"
|
||||
aria-live="assertive">
|
||||
{{ error() }}
|
||||
</div>
|
||||
} @else {
|
||||
<div
|
||||
data-what="profile-content"
|
||||
data-which="user-profile"
|
||||
role="region"
|
||||
aria-labelledby="profile-title">
|
||||
<h2 id="profile-title">{{ user().name }}</h2>
|
||||
<!-- profile content -->
|
||||
</div>
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
**See [combined-patterns.md](references/combined-patterns.md) for complete form examples, product listings, shopping carts, modal dialogs, navigation patterns, data tables, search interfaces, notifications, and multi-step forms with all attributes properly applied.**
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
@@ -501,7 +370,6 @@ Before considering template complete:
|
||||
- [ ] Complex expressions moved to `computed()` in component
|
||||
- [ ] @defer used for below-the-fold or heavy components
|
||||
- [ ] Content projection using ng-content with proper selectors (if applicable)
|
||||
- [ ] Template references using # for local variables
|
||||
- [ ] Using @let for template-scoped variables (Angular 18.1+)
|
||||
|
||||
### E2E Attributes
|
||||
@@ -510,7 +378,6 @@ Before considering template complete:
|
||||
- [ ] All links have `data-what` and `data-which`
|
||||
- [ ] Dynamic lists use `[attr.data-*]` bindings with unique identifiers
|
||||
- [ ] No duplicate `data-which` values within the same view
|
||||
- [ ] Additional `data-*` attributes for contextual information (if needed)
|
||||
|
||||
### ARIA Accessibility
|
||||
- [ ] All interactive elements have appropriate ARIA labels
|
||||
@@ -520,16 +387,13 @@ Before considering template complete:
|
||||
- [ ] Dialogs have `role="dialog"`, `aria-modal`, and label relationships
|
||||
- [ ] Dynamic state changes reflected in ARIA attributes
|
||||
- [ ] Keyboard accessibility (tabindex, enter/space handlers where needed)
|
||||
- [ ] Screen reader testing completed (if applicable)
|
||||
|
||||
### Combined Standards
|
||||
- [ ] Every interactive element has BOTH E2E and ARIA attributes
|
||||
- [ ] Attributes organized logically in template (Angular directives → data-* → aria-*)
|
||||
- [ ] Attributes organized logically (Angular directives → data-* → aria-*)
|
||||
- [ ] Dynamic bindings use `[attr.*]` syntax correctly
|
||||
- [ ] No accessibility violations (semantic HTML preferred over ARIA)
|
||||
|
||||
---
|
||||
|
||||
## Migration Guide
|
||||
|
||||
### From Legacy Angular Syntax
|
||||
@@ -549,31 +413,27 @@ Before considering template complete:
|
||||
3. **Add ARIA attributes**: appropriate role, label, and state attributes
|
||||
4. **Test**: verify selectors work in E2E tests, validate with screen readers
|
||||
|
||||
---
|
||||
|
||||
## Reference Files
|
||||
|
||||
For detailed examples and advanced patterns, see:
|
||||
|
||||
### Angular Syntax References
|
||||
- `references/control-flow-reference.md` - @if/@for/@switch patterns
|
||||
- `references/defer-patterns.md` - Lazy loading strategies
|
||||
- `references/projection-patterns.md` - Advanced ng-content
|
||||
- `references/template-reference.md` - ng-template/ng-container
|
||||
- `references/control-flow-reference.md` - Advanced @if/@for/@switch patterns, nested loops, filtering
|
||||
- `references/defer-patterns.md` - Lazy loading strategies, Core Web Vitals, performance optimization
|
||||
- `references/projection-patterns.md` - Advanced ng-content, conditional projection, template-based patterns
|
||||
- `references/template-reference.md` - ng-template/ng-container, programmatic rendering, ViewContainerRef
|
||||
|
||||
### E2E Testing References
|
||||
- `references/e2e-attributes.md` - Complete E2E attribute patterns and conventions
|
||||
- `references/e2e-attributes.md` - Complete E2E attribute patterns, naming conventions, testing integration
|
||||
|
||||
### ARIA Accessibility References
|
||||
- `references/aria-attributes.md` - Comprehensive ARIA guidance and WCAG compliance
|
||||
- `references/aria-attributes.md` - Comprehensive ARIA guidance, roles, properties, states, WCAG compliance
|
||||
|
||||
### Combined References
|
||||
- `references/combined-patterns.md` - Real-world examples with Angular + E2E + ARIA
|
||||
- `references/combined-patterns.md` - Real-world examples with Angular + E2E + ARIA integrated
|
||||
|
||||
Search with: `grep -r "pattern" references/`
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference Summary
|
||||
|
||||
**Every interactive element needs:**
|
||||
|
||||
@@ -1,192 +1,192 @@
|
||||
---
|
||||
name: test-migration
|
||||
description: Reference patterns for Jest to Vitest test migration. Contains syntax mappings for jest→vi, Spectator→Angular Testing Library, and common matcher conversions. Auto-loaded by migration-specialist agent.
|
||||
---
|
||||
|
||||
# Jest to Vitest Migration Patterns
|
||||
|
||||
Quick reference for test framework migration syntax.
|
||||
|
||||
## Import Changes
|
||||
|
||||
```typescript
|
||||
// REMOVE
|
||||
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
|
||||
|
||||
// ADD
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
```
|
||||
|
||||
## Mock Migration
|
||||
|
||||
| Jest | Vitest |
|
||||
|------|--------|
|
||||
| `jest.fn()` | `vi.fn()` |
|
||||
| `jest.spyOn(obj, 'method')` | `vi.spyOn(obj, 'method')` |
|
||||
| `jest.mock('module')` | `vi.mock('module')` |
|
||||
| `jest.useFakeTimers()` | `vi.useFakeTimers()` |
|
||||
| `jest.advanceTimersByTime(ms)` | `vi.advanceTimersByTime(ms)` |
|
||||
| `jest.useRealTimers()` | `vi.useRealTimers()` |
|
||||
| `jest.clearAllMocks()` | `vi.clearAllMocks()` |
|
||||
| `jest.resetAllMocks()` | `vi.resetAllMocks()` |
|
||||
|
||||
## Spectator → TestBed
|
||||
|
||||
### Component Testing
|
||||
|
||||
```typescript
|
||||
// OLD (Spectator)
|
||||
const createComponent = createComponentFactory({
|
||||
component: MyComponent,
|
||||
imports: [CommonModule],
|
||||
mocks: [MyService]
|
||||
});
|
||||
let spectator: Spectator<MyComponent>;
|
||||
beforeEach(() => spectator = createComponent());
|
||||
|
||||
// NEW (TestBed)
|
||||
let fixture: ComponentFixture<MyComponent>;
|
||||
let component: MyComponent;
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [MyComponent],
|
||||
providers: [{ provide: MyService, useValue: mockService }]
|
||||
}).compileComponents();
|
||||
fixture = TestBed.createComponent(MyComponent);
|
||||
component = fixture.componentInstance;
|
||||
});
|
||||
```
|
||||
|
||||
### Service Testing
|
||||
|
||||
```typescript
|
||||
// OLD (Spectator)
|
||||
const createService = createServiceFactory({
|
||||
service: MyService,
|
||||
mocks: [HttpClient]
|
||||
});
|
||||
let spectator: SpectatorService<MyService>;
|
||||
beforeEach(() => spectator = createService());
|
||||
|
||||
// NEW (TestBed)
|
||||
let service: MyService;
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
MyService,
|
||||
{ provide: HttpClient, useValue: mockHttp }
|
||||
]
|
||||
});
|
||||
service = TestBed.inject(MyService);
|
||||
});
|
||||
```
|
||||
|
||||
## Query Selectors
|
||||
|
||||
| Spectator | Angular/Native |
|
||||
|-----------|---------------|
|
||||
| `spectator.query('.class')` | `fixture.nativeElement.querySelector('.class')` |
|
||||
| `spectator.queryAll('.class')` | `fixture.nativeElement.querySelectorAll('.class')` |
|
||||
| `spectator.query('button')` | `fixture.nativeElement.querySelector('button')` |
|
||||
|
||||
## Events
|
||||
|
||||
| Spectator | Native |
|
||||
|-----------|--------|
|
||||
| `spectator.click(element)` | `element.click()` + `fixture.detectChanges()` |
|
||||
| `spectator.typeInElement(value, el)` | `el.value = value; el.dispatchEvent(new Event('input'))` |
|
||||
| `spectator.blur(element)` | `element.dispatchEvent(new Event('blur'))` |
|
||||
|
||||
## Matchers
|
||||
|
||||
| Spectator | Standard |
|
||||
|-----------|----------|
|
||||
| `toHaveText('text')` | `expect(el.textContent).toContain('text')` |
|
||||
| `toExist()` | `toBeTruthy()` |
|
||||
| `toBeVisible()` | Check `!el.hidden && el.offsetParent !== null` |
|
||||
| `toHaveClass('class')` | `expect(el.classList.contains('class')).toBe(true)` |
|
||||
|
||||
## Async Patterns
|
||||
|
||||
```typescript
|
||||
// OLD (callback)
|
||||
it('should emit', (done) => {
|
||||
service.data$.subscribe(val => {
|
||||
expect(val).toBe(expected);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// NEW (async/await)
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
it('should emit', async () => {
|
||||
const val = await firstValueFrom(service.data$);
|
||||
expect(val).toBe(expected);
|
||||
});
|
||||
```
|
||||
|
||||
## Input/Output Testing
|
||||
|
||||
```typescript
|
||||
// Setting inputs (Angular 17.3+)
|
||||
fixture.componentRef.setInput('title', 'Test');
|
||||
fixture.detectChanges();
|
||||
|
||||
// Testing outputs
|
||||
const emitSpy = vi.fn();
|
||||
component.myOutput.subscribe(emitSpy);
|
||||
// trigger action...
|
||||
expect(emitSpy).toHaveBeenCalledWith(expectedValue);
|
||||
```
|
||||
|
||||
## Configuration Files
|
||||
|
||||
### vite.config.mts Template
|
||||
|
||||
```typescript
|
||||
/// <reference types='vitest' />
|
||||
import { defineConfig } from 'vite';
|
||||
import angular from '@analogjs/vite-plugin-angular';
|
||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
|
||||
|
||||
export default
|
||||
// @ts-expect-error - Vitest reporter tuple types have complex inference issues
|
||||
defineConfig(() => ({
|
||||
root: __dirname,
|
||||
cacheDir: '../../../node_modules/.vite/libs/[path]', // Adjust depth!
|
||||
plugins: [angular(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
|
||||
test: {
|
||||
watch: false,
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
||||
setupFiles: ['src/test-setup.ts'],
|
||||
reporters: [
|
||||
'default',
|
||||
['junit', { outputFile: '../../../testresults/junit-[name].xml' }],
|
||||
],
|
||||
coverage: {
|
||||
reportsDirectory: '../../../coverage/libs/[path]',
|
||||
provider: 'v8' as const,
|
||||
reporter: ['text', 'cobertura'],
|
||||
},
|
||||
},
|
||||
}));
|
||||
```
|
||||
|
||||
### test-setup.ts
|
||||
|
||||
```typescript
|
||||
import '@analogjs/vitest-angular/setup-zone';
|
||||
```
|
||||
|
||||
## Path Depth Reference
|
||||
|
||||
| Library Location | Relative Path Prefix |
|
||||
|-----------------|---------------------|
|
||||
| `libs/feature/ui` | `../../` |
|
||||
| `libs/feature/data-access` | `../../` |
|
||||
| `libs/domain/feature/ui` | `../../../` |
|
||||
| `libs/domain/feature/data-access/store` | `../../../../` |
|
||||
---
|
||||
name: test-migration
|
||||
description: Reference patterns for Jest to Vitest test migration. Contains syntax mappings for jest→vi, Spectator→Angular Testing Library, and common matcher conversions. Auto-loaded by migration-specialist agent.
|
||||
---
|
||||
|
||||
# Jest to Vitest Migration Patterns
|
||||
|
||||
Quick reference for test framework migration syntax.
|
||||
|
||||
## Import Changes
|
||||
|
||||
```typescript
|
||||
// REMOVE
|
||||
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
|
||||
|
||||
// ADD
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
```
|
||||
|
||||
## Mock Migration
|
||||
|
||||
| Jest | Vitest |
|
||||
|------|--------|
|
||||
| `jest.fn()` | `vi.fn()` |
|
||||
| `jest.spyOn(obj, 'method')` | `vi.spyOn(obj, 'method')` |
|
||||
| `jest.mock('module')` | `vi.mock('module')` |
|
||||
| `jest.useFakeTimers()` | `vi.useFakeTimers()` |
|
||||
| `jest.advanceTimersByTime(ms)` | `vi.advanceTimersByTime(ms)` |
|
||||
| `jest.useRealTimers()` | `vi.useRealTimers()` |
|
||||
| `jest.clearAllMocks()` | `vi.clearAllMocks()` |
|
||||
| `jest.resetAllMocks()` | `vi.resetAllMocks()` |
|
||||
|
||||
## Spectator → TestBed
|
||||
|
||||
### Component Testing
|
||||
|
||||
```typescript
|
||||
// OLD (Spectator)
|
||||
const createComponent = createComponentFactory({
|
||||
component: MyComponent,
|
||||
imports: [CommonModule],
|
||||
mocks: [MyService]
|
||||
});
|
||||
let spectator: Spectator<MyComponent>;
|
||||
beforeEach(() => spectator = createComponent());
|
||||
|
||||
// NEW (TestBed)
|
||||
let fixture: ComponentFixture<MyComponent>;
|
||||
let component: MyComponent;
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [MyComponent],
|
||||
providers: [{ provide: MyService, useValue: mockService }]
|
||||
}).compileComponents();
|
||||
fixture = TestBed.createComponent(MyComponent);
|
||||
component = fixture.componentInstance;
|
||||
});
|
||||
```
|
||||
|
||||
### Service Testing
|
||||
|
||||
```typescript
|
||||
// OLD (Spectator)
|
||||
const createService = createServiceFactory({
|
||||
service: MyService,
|
||||
mocks: [HttpClient]
|
||||
});
|
||||
let spectator: SpectatorService<MyService>;
|
||||
beforeEach(() => spectator = createService());
|
||||
|
||||
// NEW (TestBed)
|
||||
let service: MyService;
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
MyService,
|
||||
{ provide: HttpClient, useValue: mockHttp }
|
||||
]
|
||||
});
|
||||
service = TestBed.inject(MyService);
|
||||
});
|
||||
```
|
||||
|
||||
## Query Selectors
|
||||
|
||||
| Spectator | Angular/Native |
|
||||
|-----------|---------------|
|
||||
| `spectator.query('.class')` | `fixture.nativeElement.querySelector('.class')` |
|
||||
| `spectator.queryAll('.class')` | `fixture.nativeElement.querySelectorAll('.class')` |
|
||||
| `spectator.query('button')` | `fixture.nativeElement.querySelector('button')` |
|
||||
|
||||
## Events
|
||||
|
||||
| Spectator | Native |
|
||||
|-----------|--------|
|
||||
| `spectator.click(element)` | `element.click()` + `fixture.detectChanges()` |
|
||||
| `spectator.typeInElement(value, el)` | `el.value = value; el.dispatchEvent(new Event('input'))` |
|
||||
| `spectator.blur(element)` | `element.dispatchEvent(new Event('blur'))` |
|
||||
|
||||
## Matchers
|
||||
|
||||
| Spectator | Standard |
|
||||
|-----------|----------|
|
||||
| `toHaveText('text')` | `expect(el.textContent).toContain('text')` |
|
||||
| `toExist()` | `toBeTruthy()` |
|
||||
| `toBeVisible()` | Check `!el.hidden && el.offsetParent !== null` |
|
||||
| `toHaveClass('class')` | `expect(el.classList.contains('class')).toBe(true)` |
|
||||
|
||||
## Async Patterns
|
||||
|
||||
```typescript
|
||||
// OLD (callback)
|
||||
it('should emit', (done) => {
|
||||
service.data$.subscribe(val => {
|
||||
expect(val).toBe(expected);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// NEW (async/await)
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
it('should emit', async () => {
|
||||
const val = await firstValueFrom(service.data$);
|
||||
expect(val).toBe(expected);
|
||||
});
|
||||
```
|
||||
|
||||
## Input/Output Testing
|
||||
|
||||
```typescript
|
||||
// Setting inputs (Angular 17.3+)
|
||||
fixture.componentRef.setInput('title', 'Test');
|
||||
fixture.detectChanges();
|
||||
|
||||
// Testing outputs
|
||||
const emitSpy = vi.fn();
|
||||
component.myOutput.subscribe(emitSpy);
|
||||
// trigger action...
|
||||
expect(emitSpy).toHaveBeenCalledWith(expectedValue);
|
||||
```
|
||||
|
||||
## Configuration Files
|
||||
|
||||
### vite.config.mts Template
|
||||
|
||||
```typescript
|
||||
/// <reference types='vitest' />
|
||||
import { defineConfig } from 'vite';
|
||||
import angular from '@analogjs/vite-plugin-angular';
|
||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
|
||||
|
||||
export default
|
||||
// @ts-expect-error - Vitest reporter tuple types have complex inference issues
|
||||
defineConfig(() => ({
|
||||
root: __dirname,
|
||||
cacheDir: '../../../node_modules/.vite/libs/[path]', // Adjust depth!
|
||||
plugins: [angular(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
|
||||
test: {
|
||||
watch: false,
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
||||
setupFiles: ['src/test-setup.ts'],
|
||||
reporters: [
|
||||
'default',
|
||||
['junit', { outputFile: '../../../testresults/junit-[name].xml' }],
|
||||
],
|
||||
coverage: {
|
||||
reportsDirectory: '../../../coverage/libs/[path]',
|
||||
provider: 'v8' as const,
|
||||
reporter: ['text', 'cobertura'],
|
||||
},
|
||||
},
|
||||
}));
|
||||
```
|
||||
|
||||
### test-setup.ts
|
||||
|
||||
```typescript
|
||||
import '@analogjs/vitest-angular/setup-zone';
|
||||
```
|
||||
|
||||
## Path Depth Reference
|
||||
|
||||
| Library Location | Relative Path Prefix |
|
||||
|-----------------|---------------------|
|
||||
| `libs/feature/ui` | `../../` |
|
||||
| `libs/feature/data-access` | `../../` |
|
||||
| `libs/domain/feature/ui` | `../../../` |
|
||||
| `libs/domain/feature/data-access/store` | `../../../../` |
|
||||
|
||||
Reference in New Issue
Block a user