Migration Lint Command
Review and fix migrated Angular code for compliance. This command reads ALL migration guidelines and performs a comprehensive checklist review.
Arguments
$ARGUMENTS- Target path to review (e.g.,libs/mxsecurity/connection-management-page)
Workflow
Step 1: Load ALL Migration Guidelines
IMPORTANT: Read the core SKILL.md first, then reference documents as needed from ``.
Core: one-ui-migration skill
| # | Topic | File | Category | |---|-------|------|----------| | 1 | Migration Context | rules/reference/migration-context.md | Foundation | | 2 | Angular 20 Syntax | rules/reference/angular-20-syntax.md | Foundation | | 3 | DDD Architecture | rules/reference/ddd-architecture.md | Architecture | | 4 | State Management | rules/reference/state-management.md | Architecture | | 5 | Authentication | rules/tools/auth.md | Architecture | | 6 | Migration Checklist | rules/reference/migration-checklist.md | Foundation | | 7 | Pitfalls Index | rules/reference/pitfalls/index.md | Pitfalls | | 7a | Pitfalls: Translation & Layout | rules/reference/pitfalls/translation-layout.md | Pitfalls | | 7b | Pitfalls: DDD Violations | rules/reference/pitfalls/ddd-violations.md | Pitfalls | | 7c | Pitfalls: Angular Syntax | rules/reference/pitfalls/angular-syntax.md | Pitfalls | | 7d | Pitfalls: Forms & Services | rules/reference/pitfalls/forms-services.md | Pitfalls | | 8 | Validators | rules/tools/forms/validators.md | Forms | | 9 | Error Handling | rules/tools/forms/error-handling.md | Forms | | 10 | Form Patterns | rules/tools/forms/patterns.md | Forms | | 11 | Page Layout | rules/tools/ui/page-layout.md | UI Patterns | | 12a | Forms | rules/tools/ui/forms.md | UI Patterns | | 12b | Buttons | rules/tools/ui/buttons.md | UI Patterns | | 12c | Components | rules/tools/ui/components.md | UI Patterns | | 13 | Dialogs | rules/tools/ui/dialogs.md | UI Patterns | | 14 | Table Basics | rules/tools/tables/basics.md | Tables | | 15 | Table Columns | rules/tools/tables/columns.md | Tables | | 16 | Table Advanced | rules/tools/tables/advanced.md | Tables | | 17 | API Type Definitions | rules/reference/api-types.md | API | | 18 | Shared Stores | rules/reference/shared-stores.md | State |
Step 2: Read Target Files
Read all .ts, .html, and .scss files in the target path:
{target}/domain/src/lib/**/*.ts{target}/features/src/lib/**/*.ts{target}/features/src/lib/**/*.html{target}/features/src/lib/**/*.scss{target}/ui/src/lib/**/*.ts{target}/ui/src/lib/**/*.html{target}/ui/src/lib/**/*.scss{target}/shell/src/lib/**/*.ts
Step 3: Comprehensive Checklist Review
Check EVERY item from EVERY migration file:
Checklist by Category
Category 1: Foundation & Pitfalls
migration-context.md
- [ ] Project follows one-ui monorepo structure
- [ ] Uses correct scope tags (
scope:mxsecurity) - [ ] Library types match DDD pattern (
domain,features,ui,shell)
angular-syntax.md
- [ ] Uses
@ifinstead of*ngIf - [ ] Uses
@for (item of items; track item.id)instead of*ngFor - [ ] Uses
@switchinstead ofngSwitch - [ ] Uses
inject()instead of constructor injection - [ ] Uses
input()instead of@Input() - [ ] Uses
output()instead of@Output() - [ ] Uses
viewChild()/viewChildren()instead of@ViewChild()/@ViewChildren() - [ ] Uses
contentChild()/contentChildren()instead of@ContentChild()/@ContentChildren() - [ ] Uses
model()for two-way binding where appropriate - [ ] Components are standalone (default in Angular 20)
- [ ] Uses
ChangeDetectionStrategy.OnPush
migration-checklist.md
- [ ] Domain layer contains only business logic, models, services, state
- [ ] Features layer contains smart components with store injection
- [ ] UI layer contains presentational components with input/output ONLY (NO store injection)
- [ ] Shell layer contains routing configuration
- [ ] Barrel exports (
index.ts) are properly configured
pitfalls/translation-layout.md
- [ ] Translation keys match source exactly (no new keys created)
- [ ] Form layout matches source (row groupings preserved)
- [ ] No padding in page component styles
- [ ] Page uses
gl-page-contentclass (not ng-container) - [ ] Table toolbar button order: Refresh → Create/Delete (only if old code has refresh)
pitfalls/ddd-violations.md
- [ ] No form templates in features layer (extract to UI)
- [ ] No store injection in UI components
- [ ] Dialogs are in features layer (not UI)
- [ ] No business logic in features (move to domain)
- [ ] No HTTP calls in UI components
pitfalls/angular-syntax.md
- [ ] No mixing old/new control flow (
*ngIfwith@for) - [ ] All
@forloops havetrack - [ ] No NgModule patterns (use standalone)
- [ ]
langChanged()called at field level (not in computed) - [ ] Uses
inject()instead of constructor injection
pitfalls/forms-services.md
- [ ]
mutationMethod/queryMethodimported from@one-ui/mxsecurity/shared/domain(NOTshared/util) - [ ] No unnecessary
.def.tsfiles for constants - [ ] Uses
MxSnackbarService(notMatSnackBar) - [ ] Uses
controls.xxx(not.get('xxx')) - [ ] Form valueChanges uses
toSignal()in effects - [ ] Uses
EMPTY_DASHconstant (notutils.emptyDash) - [ ] Uses
oneUiNumberOnly(notappNumberOnly) - [ ] Uses
mx-password-inputfor password fields - [ ] Uses
mx-key-valuefor readonly displays - [ ] No
TRANSLOCO_SCOPEprovider (use global translation keys) - [ ] No endpoint constants (
#ENDPOINTS = {}) - inline URLs directly in API methods
Category 2: Architecture
ddd-architecture.md
- [ ] Domain layer has correct structure
- [ ] Features layer has correct structure
- [ ] UI layer has correct structure
- [ ] Shell layer has correct structure
- [ ] Dependencies flow correctly (UI → Domain, Features → UI/Domain)
- [ ] No UI components importing from features
- [ ] Shared domain utilities used where appropriate
state-management.md
- [ ] Uses NgRx SignalStore for state management
- [ ] Store follows correct naming convention (
XxxStore) - [ ] Uses
withEntities()for entity management - [ ] Uses
withRequestStatus()for loading states - [ ] Uses
withDataService()for API integration - [ ] Effects handle success/error properly
- [ ] Uses
patchState()correctly - [ ] Store is provided in correct scope
authentication.md
- [ ] Uses
AuthStorefor authentication state - [ ] Permission checks use correct patterns
- [ ] Protected routes have guards
- [ ] API calls include proper authentication headers
Category 3: Forms (forms/)
validators.md
- [ ] Uses
OneValidators.required(notValidators.required) - [ ] Uses
OneValidators.email(notValidators.email) - [ ] Uses
OneValidators.minLength(n)(notValidators.minLength) - [ ] Uses
OneValidators.maxLength(n)(notValidators.maxLength) - [ ] Uses
OneValidators.min(n)(notValidators.min) - [ ] Uses
OneValidators.max(n)(notValidators.max) - [ ] Uses
OneValidators.pattern(x)(notValidators.pattern) - [ ] Custom validators follow correct patterns
- [ ] Cross-field validators implemented correctly
- [ ] Import from
@one-ui/mxsecurity/shared/domain
error-handling.md
- [ ] Form errors displayed using
<mat-error> - [ ] Error messages use translation keys
- [ ] Async validators handle errors
- [ ] API errors displayed to user
- [ ] Uses
MxSnackbarServicefor notifications
patterns.md
- [ ] Uses
NonNullableFormBuilder(notFormBuilder) - [ ] Form controls accessed via
controls.xxx(not.get('xxx')) - [ ]
getRawValue()used for form submission - [ ] Dynamic forms use
FormArraycorrectly - [ ] Form reset/patch follows patterns
- [ ] Forms in UI layer use
input()/output()for data flow
Category 4: UI Patterns (ui/)
ui/page-layout.md
- [ ] Root wrapper has
class="gl-page-content" - [ ] Uses
<one-ui-breadcrumb />at top - [ ] Uses
<mx-page-title [title]="t('...')">for page title - [ ] Content wrapped in
class="content-wrapper"(NOTmat-card) - [ ] Uses
*transloco="let t"pattern - [ ] Section titles use
class="gl-title-md"
buttons.md
- [ ] Submit button uses
mat-flat-button color="primary" - [ ] Cancel button uses
mat-button - [ ] Toolbar buttons use
mat-stroked-button - [ ] NO
mat-raised-button(usemat-flat-button) - [ ] Loading state uses
[mxButtonIsLoading]directive - [ ] Disabled state properly handles
noPermission()
forms.md
- [ ] Single form field NOT wrapped in
<div class="form-row"> - [ ] Multiple fields on same row use
<div class="form-row">
dialogs.md
- [ ] Uses shared dialog config (
smallDialogConfig,mediumDialogConfig,largeDialogConfig) - [ ] Has
viewContainerRef: this.#viewContainerRefwhen dialog uses store - [ ]
ConfirmDialogComponentresult checked as boolean (NOTresult?.confirm) - [ ] Dialog data typed properly
- [ ] Dialog returns correct type
- [ ]
afterClosed()subscription cleaned up properly
Category 5: Tables (tables/)
basics.md
- [ ] Uses
<mx-table>component - [ ] Column definitions use
MxColumnDef[] - [ ] Has
data-testidattributes - [ ] Loading state shows skeleton
- [ ] Empty state handled properly
columns.md
- [ ] Column types match data
- [ ] Custom templates import
MatTableModuleandMatSortModule - [ ] Link columns use proper navigation
- [ ] Status columns use proper styling
- [ ] Action columns positioned correctly
advanced.md
- [ ] Selection implemented correctly (if applicable)
- [ ] Sorting configured properly
- [ ] Pagination implemented (if applicable)
- [ ] Filtering works correctly (if applicable)
- [ ] Toolbar buttons use
mat-stroked-button
Category 6: API & Services
api-types.md
- [ ] API types defined in domain layer
- [ ] Request/Response types properly typed
- [ ] Uses proper HTTP methods
- [ ] Error responses typed
- [ ] Enums defined for constants
- [ ] NO endpoint constants (
#ENDPOINTS = {...}) - inline URL strings directly in methods
shared-stores.md
- [ ] Uses shared stores where appropriate (e.g.,
TimeZoneStore) - [ ] Shared store initialization handled
- [ ] Cross-feature state accessed via shared stores
- [ ] No duplicate state management
Category 7: Additional Checks
Translation (Transloco)
- [ ] Uses
*transloco="let t"or@let t = translocoT - [ ] NO
translatepipe - [ ] Translation keys follow naming convention
- [ ] Parameters passed correctly to
t()
Directives
- [ ]
oneUiNumberOnly(notappNumberOnly) - [ ] Custom directives use
one-uiprefix
Services
- [ ]
MxSnackbarService(notMatSnackBar) - [ ] Snackbar:
snackBar.open(msg)(notsnackBar.open(msg, '', { duration: 3000 }))
Constants
- [ ]
EMPTY_DASHfrom@one-ui/mxsecurity/shared/domain(notutils.emptyDash)
Imports
- [ ] No circular imports
- [ ] Imports from correct library paths
- [ ] No relative imports crossing library boundaries
Password Fields
- [ ] Uses
<mx-password-input>for password fields
Readonly Display
- [ ] Uses
<mx-key-value>for readonly key-value displays
Step 4: Fix Issues
Auto-fix the following issues:
| Issue | Fix |
|-------|-----|
| mat-raised-button | → mat-flat-button |
| *ngIf | → @if |
| *ngFor | → @for (item of items; track item.id) |
| Validators.required | → OneValidators.required |
| Validators.email | → OneValidators.email |
| Validators.minLength(n) | → OneValidators.minLength(n) |
| Validators.maxLength(n) | → OneValidators.maxLength(n) |
| .get('xxx') | → controls.xxx |
| class="section-title" | → class="gl-title-md" |
| appNumberOnly | → oneUiNumberOnly |
| this.utils.emptyDash | → EMPTY_DASH |
| readonly #ENDPOINTS = { | → Remove constant, inline URLs directly in each method |
| from '@one-ui/mxsecurity/shared/util' (for mutationMethod/queryMethod) | → from '@one-ui/mxsecurity/shared/domain' |
Step 5: Generate Report
Output a compliance report:
## Migration Lint Report
**Path:** {target_path}
**Date:** {current_date}
### Checklist Summary
| Category | Files Checked | Pass | Fail | N/A |
|----------|---------------|------|------|-----|
| Foundation (01, 02, 06) | 3 | Pass | Fail | - |
| Architecture (03, 04, 05) | 3 | Pass | Fail | - |
| Pitfalls (08, 08a, 08b, 08c, 08d) | 5 | Pass | Fail | - |
| Forms (09a, 09b, 09c) | 3 | Pass | Fail | - |
| UI Patterns (10a, 10b, 10c) | 3 | Pass | Fail | - |
| Tables (11a, 11b, 11c) | 3 | Pass | Fail | - |
| API & Services (12, 13) | 2 | Pass | Fail | - |
| **Total** | **22** | **X** | **Y** | **Z** |
### Issues Fixed
| File | Line | Issue | Fix Applied |
|------|------|-------|-------------|
| component.html | 15 | mat-raised-button | Changed to mat-flat-button |
### Issues Found (Manual Review Needed)
| File | Line | Issue | Recommendation | Guide Ref |
|------|------|-------|----------------|-----------|
| component.ts | 42 | Missing viewContainerRef | Add viewContainerRef to dialog config | dialogs.md |
| store.ts | 20 | Store in UI layer | Move to features layer | ddd-architecture.md |
### Detailed Checklist Results
#### migration-context.md (Pass)
- [x] Project follows one-ui monorepo structure
- [x] Uses correct scope tags
#### angular-syntax.md (Warning)
- [x] Uses @if instead of *ngIf
- [ ] Uses inject() instead of constructor injection (line 45)
... (continue for all files)
### Summary
- **Migration Files Checked:** 22/22
- **Auto-fixed:** X issues
- **Manual review needed:** Y issues
- **Passed checks:** Z items
- **Failed checks:** W items
Quick Reference
Button Types
<!-- Form submit button -->
<button mat-flat-button color="primary" type="submit">Apply</button>
<!-- Table toolbar button -->
<button mat-stroked-button>Create</button>
<!-- Cancel/secondary button -->
<button mat-button type="button">Cancel</button>
Page Layout
<div *transloco="let t" class="gl-page-content">
<one-ui-breadcrumb />
<mx-page-title [title]="t('page.title')" />
<div class="content-wrapper">
<!-- content here, NO mat-card -->
</div>
</div>
Form Row
<!-- Single field - NO form-row wrapper -->
<mat-form-field>
<mat-label>{{ t('label') }}</mat-label>
<input matInput formControlName="field" />
</mat-form-field>
<!-- Multiple fields on same row - USE form-row wrapper -->
<div class="form-row">
<mat-form-field>...</mat-form-field>
<mat-form-field>...</mat-form-field>
</div>
Dialog Config
import { mediumDialogConfig } from '@one-ui/mxsecurity/shared/domain';
this.#dialog.open(MyDialog, {
...mediumDialogConfig,
data: dialogData,
viewContainerRef: this.#viewContainerRef // Required if dialog uses store
});
Validators
import { OneValidators } from '@one-ui/mxsecurity/shared/domain';
this.#fb.group({
name: ['', [OneValidators.required, OneValidators.maxLength(32)]],
email: ['', [OneValidators.required, OneValidators.email]],
port: ['', [OneValidators.required, OneValidators.min(1), OneValidators.max(65535)]]
});
ConfirmDialogComponent
// ConfirmDialogComponent returns boolean, NOT { confirm: boolean }
const dialogRef = this.#dialog.open<ConfirmDialogComponent, ConfirmDialogData, boolean>(
ConfirmDialogComponent,
{
...mediumDialogConfig,
viewContainerRef: this.#viewContainerRef,
data: { title: '...', desc: '...' }
}
);
dialogRef.afterClosed().subscribe((result) => {
if (result) { // result is boolean
this.doSomething();
}
});
Snackbar Service
// Use MxSnackbarService instead of MatSnackBar
import { MxSnackbarService } from '@moxa/formoxa/mx-snackbar';
readonly #snackBar = inject(MxSnackbarService);
// Simple usage
this.#snackBar.open('Success message');
// Persistent message (no auto-dismiss)
this.#snackBar.open('Saving...', { duration: 0 });
Section Title
<!-- Use gl-title-md class (global style) -->
<h3 class="gl-title-md">{{ t('section.title') }}</h3>
<!-- NOT section-title (requires custom SCSS) -->
<h3 class="section-title">{{ t('section.title') }}</h3>
Form Controls Access
// .get() - no type safety
const value = this.form.get('username')?.value;
// controls.xxx - type safe
const value = this.form.controls.username.value;
// In ValidatorFn - use type assertion
#myValidator(): ValidatorFn {
return (formGroup: AbstractControl): null => {
const controls = (formGroup as typeof this.form).controls;
const fieldValue = controls.fieldName.value;
return null;
};
}
Password Input
<!-- Use mx-password-input for password fields -->
<mx-password-input
formControlName="password"
[maxlength]="32"
></mx-password-input>
Key-Value Display
<!-- Use mx-key-value for readonly displays -->
<mx-key-value [key]="t('label')" [value]="displayValue"></mx-key-value>
UI Component Pattern
// UI components use input()/output() ONLY - NO store injection
export class MyUiComponent {
readonly data = input<MyData>();
readonly isLoading = input<boolean>(false);
readonly save = output<MyData>();
readonly cancel = output<void>();
}
Features Component Pattern
// Features components can inject stores
export class MyFeatureComponent {
readonly #store = inject(MyStore);
readonly data = this.#store.data;
readonly isLoading = this.#store.isLoading;
}
Scan to join WeChat group