@rt-tools/agent-kit 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +235 -18
- package/assets/agents/business-analyst.md +74 -0
- package/assets/agents/project-manager.md +70 -0
- package/assets/agents/qa-engineer.md +72 -0
- package/assets/agents/skill-curator.md +110 -0
- package/assets/agents/spec-critic.md +44 -0
- package/assets/agents/spec-writer.md +50 -0
- package/assets/checks/board.github.mjs +286 -0
- package/assets/checks/check-board.github.mjs +188 -0
- package/assets/checks/check-doc-paths.mjs +163 -0
- package/assets/checks/check-dupes.mjs +277 -0
- package/assets/checks/check-lib-layers.mjs +573 -0
- package/assets/checks/check-reuse.mjs +208 -0
- package/assets/checks/check-schema-drift.mjs +186 -0
- package/assets/checks/check-specs.mjs +1007 -0
- package/assets/checks/check-styles.mjs +109 -0
- package/assets/checks/rt-kit-checks.config.mjs +134 -0
- package/assets/checks/task-new.github.mjs +198 -0
- package/assets/commands/skill-curator.md +70 -0
- package/assets/defaults/gate-map.sh +100 -0
- package/assets/defaults/project.sh +179 -0
- package/assets/hooks/browser-device-id.sh +20 -0
- package/assets/hooks/browser-guard-device-id.sh +28 -0
- package/assets/hooks/browser-guard-no-asking.sh +27 -0
- package/assets/hooks/browser-guard-no-listing.sh +18 -0
- package/assets/hooks/browser-guard-no-other-drivers.sh +79 -0
- package/assets/hooks/browser-guard-require-select.sh +54 -0
- package/assets/hooks/commit-msg.sh +26 -0
- package/assets/hooks/constitution-index.sh +43 -0
- package/assets/hooks/dev-server-guard.sh +115 -0
- package/assets/hooks/docs-guard.sh +282 -0
- package/assets/hooks/git-guard-delivery.sh +167 -0
- package/assets/hooks/git-guard-main.sh +73 -0
- package/assets/hooks/git-guard-push-tests.sh +94 -0
- package/assets/hooks/glossary-load.sh +23 -0
- package/assets/hooks/lint-after-edit.sh +219 -0
- package/assets/hooks/qa-dataid-guard.sh +121 -0
- package/assets/hooks/reuse-first-guard.sh +154 -0
- package/assets/hooks/skill-gate-rearm.sh +23 -0
- package/assets/hooks/skill-gate.sh +128 -0
- package/assets/hooks/skill-loaded.sh +21 -0
- package/assets/hooks/sql-guard.sh +679 -0
- package/assets/hooks/task-context-load.sh +100 -0
- package/assets/hooks/task-flow-guard.sh +107 -0
- package/assets/laws/{access.md → application/access.md} +1 -4
- package/assets/laws/{locales.md → application/locales.md} +1 -3
- package/assets/laws/application/money.md +41 -0
- package/assets/laws/application/ownership.md +32 -0
- package/assets/laws/{search-visibility.md → application/search-visibility.md} +1 -1
- package/assets/laws/code-structure.md +7 -6
- package/assets/laws/delivery.md +53 -3
- package/assets/laws/entity-editing.md +49 -55
- package/assets/laws/entity-models.md +4 -14
- package/assets/laws/frontend-application.md +5 -5
- package/assets/laws/lib-imports.md +14 -1
- package/assets/laws/lists.md +33 -0
- package/assets/laws/navigation.md +40 -0
- package/assets/laws/project-documentation.md +17 -8
- package/assets/laws/reuse-first.md +26 -21
- package/assets/laws/shared-code.md +13 -1
- package/assets/laws/verifiability.md +17 -1
- package/assets/laws/work-conduct.md +48 -0
- package/assets/patterns/admin-lists-screen.md +131 -0
- package/assets/patterns/admin-nav-item.md +71 -0
- package/assets/patterns/angular-patterns-state.md +101 -0
- package/assets/patterns/api-layer-pair.md +88 -0
- package/assets/patterns/browser-verification-measure.md +86 -0
- package/assets/patterns/browser-verification-stand.md +143 -0
- package/assets/patterns/component-structure-new.md +99 -0
- package/assets/patterns/dependencies-upgrade.md +65 -0
- package/assets/patterns/doc-style-sweep.md +137 -0
- package/assets/patterns/doc-style-write.md +109 -0
- package/assets/patterns/entity-aside.md +136 -0
- package/assets/patterns/entity-models-new.md +124 -0
- package/assets/patterns/entity-store.md +91 -0
- package/assets/patterns/git-workflow-commit.azure.md +259 -0
- package/assets/patterns/git-workflow-commit.github.md +333 -0
- package/assets/patterns/git-workflow-commit.gitlab.md +283 -0
- package/assets/patterns/git-workflow-merge.md +99 -0
- package/assets/patterns/git-workflow-migration.md +88 -0
- package/assets/patterns/git-workflow-restart.md +49 -0
- package/assets/patterns/lib-layers-move.md +95 -0
- package/assets/patterns/lib-layers-new.md +82 -0
- package/assets/patterns/ownership-scope-resolve.md +69 -0
- package/assets/patterns/permissions-procedure.md +71 -0
- package/assets/patterns/platform-access-di.md +84 -0
- package/assets/patterns/pricing-quote.md +71 -0
- package/assets/patterns/reuse-first-extend.md +73 -0
- package/assets/patterns/seo-page.md +104 -0
- package/assets/patterns/seo-verify.md +83 -0
- package/assets/patterns/shared-code-new.md +86 -0
- package/assets/patterns/spec-driven-domain.md +107 -0
- package/assets/patterns/spec-driven-rule.md +127 -0
- package/assets/patterns/styling-bem-component.md +88 -0
- package/assets/patterns/styling-bem-layout.md +73 -0
- package/assets/patterns/task-flow-close.md +90 -0
- package/assets/patterns/task-flow-resume.md +94 -0
- package/assets/patterns/task-flow-start.md +117 -0
- package/assets/patterns/testing-e2e.md +92 -0
- package/assets/patterns/testing-unit.md +117 -0
- package/assets/patterns/translations-key.md +64 -0
- package/assets/patterns/ts-procedure.md +65 -0
- package/assets/rules/angular-patterns.md +71 -0
- package/assets/rules/api-layer.md +71 -0
- package/assets/rules/browser-verification.md +87 -0
- package/assets/rules/component-structure.md +64 -0
- package/assets/rules/dependencies.md +66 -0
- package/assets/rules/doc-style.md +103 -0
- package/assets/rules/entity-conventions.md +78 -0
- package/assets/rules/entity-models.md +70 -0
- package/assets/rules/git-workflow.azure.md +116 -0
- package/assets/rules/git-workflow.github.md +123 -0
- package/assets/rules/git-workflow.gitlab.md +113 -0
- package/assets/rules/lib-layers.md +80 -0
- package/assets/rules/lists.md +73 -0
- package/assets/rules/navigation.md +78 -0
- package/assets/rules/ownership-scope.md +63 -0
- package/assets/rules/permissions.md +70 -0
- package/assets/rules/platform-access.md +77 -0
- package/assets/rules/pricing.md +64 -0
- package/assets/rules/reuse-first.md +83 -0
- package/assets/rules/seo.md +71 -0
- package/assets/rules/shared-code.md +70 -0
- package/assets/rules/spec-driven.md +135 -0
- package/assets/rules/styling-bem.md +74 -0
- package/assets/rules/task-flow.md +110 -0
- package/assets/rules/testing.md +100 -0
- package/assets/rules/translations.md +69 -0
- package/assets/rules/typescript-conventions.md +76 -0
- package/assets/skills/agent-kit.md +81 -0
- package/assets/skills/write-a-skill.md +108 -0
- package/assets/templates/gate-map.sh +45 -0
- package/assets/templates/implementation.md +44 -0
- package/assets/templates/pattern.md +5 -1
- package/assets/templates/project.sh +54 -0
- package/assets/templates/rule.md +12 -23
- package/assets/variants.json +20 -0
- package/assets/workflows/feature.js +134 -0
- package/assets/workflows/plan.js +150 -0
- package/bin/agent-kit.d.ts.map +1 -1
- package/bin/agent-kit.js +78 -5
- package/bin/agent-kit.js.map +1 -1
- package/bin/prompt.d.ts +5 -0
- package/bin/prompt.d.ts.map +1 -1
- package/bin/prompt.js +19 -7
- package/bin/prompt.js.map +1 -1
- package/index.d.ts +1 -0
- package/index.d.ts.map +1 -1
- package/index.js +1 -0
- package/index.js.map +1 -1
- package/lib/assets.d.ts +14 -1
- package/lib/assets.d.ts.map +1 -1
- package/lib/assets.js +23 -2
- package/lib/assets.js.map +1 -1
- package/lib/catalog.d.ts +52 -5
- package/lib/catalog.d.ts.map +1 -1
- package/lib/catalog.js +104 -16
- package/lib/catalog.js.map +1 -1
- package/lib/commands.d.ts +22 -1
- package/lib/commands.d.ts.map +1 -1
- package/lib/commands.js +218 -11
- package/lib/commands.js.map +1 -1
- package/lib/companion.d.ts +57 -0
- package/lib/companion.d.ts.map +1 -0
- package/lib/companion.js +60 -0
- package/lib/companion.js.map +1 -0
- package/lib/config.d.ts +42 -2
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +60 -2
- package/lib/config.js.map +1 -1
- package/lib/freshness.d.ts +14 -0
- package/lib/freshness.d.ts.map +1 -0
- package/lib/freshness.js +116 -0
- package/lib/freshness.js.map +1 -0
- package/lib/hooks-map.d.ts +24 -0
- package/lib/hooks-map.d.ts.map +1 -0
- package/lib/hooks-map.js +72 -0
- package/lib/hooks-map.js.map +1 -0
- package/lib/integrity.d.ts +36 -0
- package/lib/integrity.d.ts.map +1 -0
- package/lib/integrity.js +44 -0
- package/lib/integrity.js.map +1 -0
- package/lib/picker.d.ts +11 -1
- package/lib/picker.d.ts.map +1 -1
- package/lib/picker.js +44 -6
- package/lib/picker.js.map +1 -1
- package/lib/stamp.d.ts +2 -5
- package/lib/stamp.d.ts.map +1 -1
- package/lib/stamp.js +25 -10
- package/lib/stamp.js.map +1 -1
- package/lib/sync.d.ts +29 -0
- package/lib/sync.d.ts.map +1 -1
- package/lib/sync.js +78 -4
- package/lib/sync.js.map +1 -1
- package/lib/variants.d.ts +44 -0
- package/lib/variants.d.ts.map +1 -0
- package/lib/variants.js +82 -0
- package/lib/variants.js.map +1 -0
- package/package.json +1 -1
- package/rt-tools-agent-kit-0.4.0.tgz +0 -0
- package/assets/laws/admin-lists.md +0 -35
- package/assets/laws/admin-navigation.md +0 -38
- package/rt-tools-agent-kit-0.2.0.tgz +0 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: angular-patterns-state
|
|
3
|
+
kind: pattern
|
|
4
|
+
rule: angular-patterns
|
|
5
|
+
description: Паттерн правила angular-patterns. Брать при объявлении состояния и потоков в классе Angular — готовые сигналы, производные значения, состояние сервиса, долгоживущая подписка с источником действия. Не брать для раскладки файла компонента — это правило component-structure.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Состояние и потоки
|
|
9
|
+
|
|
10
|
+
Паттерн правила `angular-patterns`. Что при этом должно быть верно — закон
|
|
11
|
+
`docs/constitution/frontend-application.md`.
|
|
12
|
+
|
|
13
|
+
## Когда брать
|
|
14
|
+
|
|
15
|
+
- Объявляется состояние компонента или сервиса.
|
|
16
|
+
- Появляется поток, на который надо подписаться.
|
|
17
|
+
- Значение считается из другого значения.
|
|
18
|
+
|
|
19
|
+
## Сигнальный API входов и выходов
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
public readonly data: InputSignal<Item[]> = input.required<Item[]>();
|
|
23
|
+
public readonly isMobile: InputSignal<boolean | undefined> = input<boolean>();
|
|
24
|
+
public readonly save: OutputEmitterRef<void> = output<void>();
|
|
25
|
+
|
|
26
|
+
protected readonly myButton: Signal<ElementRef | undefined> = viewChild<ElementRef>('button');
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Декораторов `@Input()`, `@Output()`, `@ViewChild()`, `@ContentChild()` и их множественных пар
|
|
30
|
+
в дереве нет.
|
|
31
|
+
|
|
32
|
+
## Производное значение — `computed`, а не эффект
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
protected readonly items: WritableSignal<Item[]> = signal<Item[]>([]);
|
|
36
|
+
protected readonly itemCount: Signal<number> = computed((): number => this.items().length);
|
|
37
|
+
protected readonly hasItems: Signal<boolean> = computed((): boolean => this.itemCount() > 0);
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
✗ effect((): void => { this.count.set(this.items().length); });
|
|
42
|
+
✓ protected readonly count: Signal<number> = computed((): number => this.items().length);
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Геттера в компоненте не заводить: он пересчитывается на каждой перерисовке, и цена его не
|
|
46
|
+
видна ни в одном месте кода.
|
|
47
|
+
|
|
48
|
+
## Состояние сервиса
|
|
49
|
+
|
|
50
|
+
Наружу — только чтение:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
@Injectable({ providedIn: 'root' })
|
|
54
|
+
export class DomainStateService {
|
|
55
|
+
readonly #items: WritableSignal<Item[]> = signal<Item[]>([]);
|
|
56
|
+
|
|
57
|
+
public readonly items: Signal<Item[]> = this.#items.asReadonly();
|
|
58
|
+
public readonly itemCount: Signal<number> = computed((): number => this.#items().length);
|
|
59
|
+
|
|
60
|
+
public addItem(item: Item): void {
|
|
61
|
+
this.#items.update((items: Item[]): Item[] => [...items, item]);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Подписка объявляется один раз
|
|
67
|
+
|
|
68
|
+
Метод действия толкает значение в источник, подписка живёт в конструкторе:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
readonly #loadSource: Subject<void> = new Subject<void>();
|
|
72
|
+
readonly #destroyRef: DestroyRef = inject(DestroyRef);
|
|
73
|
+
|
|
74
|
+
constructor() {
|
|
75
|
+
this.#loadSource
|
|
76
|
+
.pipe(
|
|
77
|
+
switchMap((): Observable<IPromoCode.ListResult> => this.#api.getList(this.#query())),
|
|
78
|
+
takeUntilDestroyed(this.#destroyRef)
|
|
79
|
+
)
|
|
80
|
+
.subscribe();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
protected reload(): void {
|
|
84
|
+
this.#loadSource.next();
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Оператор выбирается по тому, что делать с предыдущим запросом: список берёт последний ответ
|
|
89
|
+
(`switchMap`), кнопка не плодит дублей (`exhaustMap`), соседние строки идут независимо
|
|
90
|
+
(`mergeMap`).
|
|
91
|
+
|
|
92
|
+
## Частые промахи
|
|
93
|
+
|
|
94
|
+
- `.subscribe()` внутри метода: правило линтера отбивает, а вместе с ним отбивается и гонка
|
|
95
|
+
ответов на быстрых нажатиях.
|
|
96
|
+
- Подписка без `takeUntilDestroyed`: она переживает владельца и держит уничтоженный экран в
|
|
97
|
+
памяти.
|
|
98
|
+
- Поле-поток без суффикса `Source`: поток и значение в коде становятся неотличимы.
|
|
99
|
+
- `inject()` вместо параметров конструктора — везде, включая базовые классы.
|
|
100
|
+
- `untracked()` там, где эффекту не нужна зависимость от сигнала: без него эффект просыпается
|
|
101
|
+
на каждое чужое изменение.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: api-layer-pair
|
|
3
|
+
kind: pattern
|
|
4
|
+
rule: api-layer
|
|
5
|
+
description: Паттерн правила api-layer. Брать при заведении или правке слоя api фронтового домена — готовые фасад и сервис, вход выборки, конвертер страницы, типы порядка и отбора в неймспейсе сущности. Не брать для модели и её маппера — это паттерн entity-models-new.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Фасад и сервис домена
|
|
9
|
+
|
|
10
|
+
Паттерн правила `api-layer`. Что при этом должно быть верно — закон
|
|
11
|
+
`docs/constitution/frontend-application.md`.
|
|
12
|
+
|
|
13
|
+
## Когда брать
|
|
14
|
+
|
|
15
|
+
- Заводится слой `api` нового домена.
|
|
16
|
+
- Список переводится на общую выборку.
|
|
17
|
+
- Появляется новая процедура, за которой ходит экран.
|
|
18
|
+
|
|
19
|
+
## Фасад
|
|
20
|
+
|
|
21
|
+
Принимает запрос контракта, отдаёт ответ контракта, ожидание заворачивает в поток. Ни выборки,
|
|
22
|
+
ни перевода моделей в нём нет:
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
@Injectable({ providedIn: 'root' })
|
|
26
|
+
export class PromoCodeApiFacade implements IListApiFacade<
|
|
27
|
+
MessageInitShape<typeof ListPromoCodesRequestSchema>,
|
|
28
|
+
ListPromoCodesResponse,
|
|
29
|
+
GetPromoCodeResponse
|
|
30
|
+
> {
|
|
31
|
+
readonly #client: Client<typeof PricingService> = injectConnectClient(PricingService);
|
|
32
|
+
|
|
33
|
+
public getList(request: MessageInitShape<typeof ListPromoCodesRequestSchema>): Observable<ListPromoCodesResponse> {
|
|
34
|
+
return from(this.#client.listPromoCodes(request));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Метод, которого у домена нет, не объявляется: список читают все, правят не все.
|
|
40
|
+
|
|
41
|
+
## Сервис
|
|
42
|
+
|
|
43
|
+
Принимает доменные модели, отдаёт их же. Тип контракта до стора и шаблона не доходит:
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
export class PromoCodeApiService implements IListApiService<
|
|
47
|
+
IPromoCode.State,
|
|
48
|
+
EPromoCodeSortProperty,
|
|
49
|
+
EPromoCodeFilterProperty,
|
|
50
|
+
IPromoCode.Draft
|
|
51
|
+
> {
|
|
52
|
+
public getList(query: IPromoCode.Query): Observable<IPromoCode.ListResult> {
|
|
53
|
+
return this.#facade
|
|
54
|
+
.getList({ query: this.#queryMapper.mapTo(query) })
|
|
55
|
+
.pipe(
|
|
56
|
+
map((response: ListPromoCodesResponse): IPromoCode.ListResult =>
|
|
57
|
+
convertPaginationApiModelToStateModel((item: PromoCodeInfo): IPromoCode.State => this.#mapper.mapFrom(item), response)
|
|
58
|
+
)
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`getList` принимает выборку и больше ничего: объект, к которому привязан список, тип фида,
|
|
65
|
+
состояние подписки — это условия отбора, и лежат они в `filterModel`.
|
|
66
|
+
|
|
67
|
+
## Типы выборки в неймспейсе сущности
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
export type Query = IList.Query.State<EPromoCodeSortProperty, EPromoCodeFilterProperty>;
|
|
71
|
+
export type ListResult = IList.Result.State<IPromoCode.State, EPromoCodeSortProperty, EPromoCodeFilterProperty>;
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Перечисления `EPromoCodeSortProperty` и `EPromoCodeFilterProperty` объявляются в модели рядом с
|
|
75
|
+
сущностью и повторяют набор имён, по которым сортирует и отбирает сервер этого домена.
|
|
76
|
+
|
|
77
|
+
## Частые промахи
|
|
78
|
+
|
|
79
|
+
- Промежуточный объект между ответом и моделью: ответ ложится в конвертер целиком.
|
|
80
|
+
- Выборка из своего запроса вместо применённой из ответа: умолчание сервера и отброшенное им
|
|
81
|
+
условие экран иначе не увидит.
|
|
82
|
+
- Второй вход рядом с выборкой (`propertyId`, `feedType`): отбор, живущий отдельно, не виден ни
|
|
83
|
+
стору, ни адресу.
|
|
84
|
+
- Голая `string` в поле порядка: имя, по которому сервер не сортирует, компилируется и падает
|
|
85
|
+
запросом.
|
|
86
|
+
- Один класс на две сущности: подмена источника одной потянет за собой правку другой.
|
|
87
|
+
- Промисный сервис в новом сторе: основа списочного стора работает потоками.
|
|
88
|
+
- Своя копия общих мапперов страницы, порядка и отбора — её ловит `npm run check:dupes`.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: browser-verification-measure
|
|
3
|
+
kind: pattern
|
|
4
|
+
rule: browser-verification
|
|
5
|
+
description: Паттерн правила browser-verification. Брать, когда вывод о вёрстке надо подкрепить числом — готовые замеры, разбивка вычисленного значения по всем узлам, узкий экран через iframe, ловушки инструмента computer. Не брать для подъёма стенда — это паттерн browser-verification-stand.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Замер вместо взгляда
|
|
9
|
+
|
|
10
|
+
Паттерн правила `browser-verification`. Что при этом должно быть верно — закон
|
|
11
|
+
`docs/constitution/verifiability.md`.
|
|
12
|
+
|
|
13
|
+
## Когда брать
|
|
14
|
+
|
|
15
|
+
- Правилась вёрстка, и надо подтвердить результат.
|
|
16
|
+
- Экран выглядит не так, а искать нечего: поиск по коду отвечает «чисто».
|
|
17
|
+
- Проверяется узкий экран.
|
|
18
|
+
|
|
19
|
+
## Вывод подкрепляется числом
|
|
20
|
+
|
|
21
|
+
`getComputedStyle`, `getBoundingClientRect`, контраст, совпадение центров, попадание во
|
|
22
|
+
вьюпорт. «Выглядит нормально» результатом проверки не является.
|
|
23
|
+
|
|
24
|
+
Замер отвечает только на тот вопрос, который задали. Совпадение перечисленных свойств ничего
|
|
25
|
+
не говорит о правиле, которого в списке замера нет: строки попапа профиля сошлись с образцом
|
|
26
|
+
по отступам, кеглю и скруглению, а фон на наведении образец в этом месте не красит вовсе —
|
|
27
|
+
полноширинная подсветка держалась два круга при верных числах. Если исходники образца
|
|
28
|
+
доступны, расхождение ищется чтением, а замер остаётся проверкой результата.
|
|
29
|
+
|
|
30
|
+
Вид элемента, которого сегодня не видно ни на одном экране, замером не подтверждается, и
|
|
31
|
+
правило о нём остаётся гипотезой.
|
|
32
|
+
|
|
33
|
+
## Значение, которого в коде нет
|
|
34
|
+
|
|
35
|
+
Гарнитуру, `line-height`, цвет и `appearance` элементам формы задаёт браузер, и в дереве этих
|
|
36
|
+
значений нет. Поиск по коду на такой дефект отвечает «чисто», линт и сборка молчат. Ищется
|
|
37
|
+
разбивкой вычисленного значения по всем узлам страницы, а не замером у пары элементов:
|
|
38
|
+
|
|
39
|
+
```javascript
|
|
40
|
+
[...document.querySelectorAll('*')].reduce((acc, el) => {
|
|
41
|
+
const key = getComputedStyle(el).fontFamily;
|
|
42
|
+
acc[key] = (acc[key] ?? 0) + 1;
|
|
43
|
+
return acc;
|
|
44
|
+
}, {});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Так на дашборде админки нашёлся 91 контрол из 106, набранный не той гарнитурой, и 13 из 87 на
|
|
48
|
+
главной сайта. Счёт годится любому наследуемому свойству: смотрится не одно значение, а число
|
|
49
|
+
узлов с неожиданным.
|
|
50
|
+
|
|
51
|
+
## Узкий экран
|
|
52
|
+
|
|
53
|
+
`resize_window` не работает, когда Chrome в полноэкранном режиме: инструмент рапортует успех,
|
|
54
|
+
`innerWidth` не меняется, медиазапросы остаются десктопными. Узкие ширины проверять во
|
|
55
|
+
вложенном iframe нужной ширины — внутри него `matchMedia` считается от рамки; при
|
|
56
|
+
`width: 375px` и `border: 2px` внутренний `innerWidth` равен 371.
|
|
57
|
+
|
|
58
|
+
Правка числа элементов в контейнере — это правка раскладки: она проверяется при 375, а не
|
|
59
|
+
только кодами ответа.
|
|
60
|
+
|
|
61
|
+
## Ловушки инструмента `computer`
|
|
62
|
+
|
|
63
|
+
- Координаты клика — координаты **скриншота**, а не CSS-пиксели: при вьюпорте 2560 скриншот
|
|
64
|
+
приходит шириной 1568, и клик по «увиденной» координате уходит мимо, давая ложный сигнал.
|
|
65
|
+
Пересчитывать по фактическому масштабу либо целиться через `find`.
|
|
66
|
+
- Область `zoom` должна целиком лежать внутри вьюпорта.
|
|
67
|
+
- Между кликами обязателен `await`: синхронный цикл «кликнул — прочитал DOM» читает состояние
|
|
68
|
+
до перерисовки и возвращает устаревшие значения.
|
|
69
|
+
- `select_browser` протухает через 300 секунд. На длинной проверке это срабатывает посреди
|
|
70
|
+
работы — это не сбой стенда, повторить вызов и продолжить. Закреплённый профиль — тот, что записан в дереве, идентификатор `062b17db-ae82-4264-927c-e9904d0dd5be`.
|
|
71
|
+
|
|
72
|
+
## Поведение роутера воспроизводится нажатиями
|
|
73
|
+
|
|
74
|
+
Подстановка адреса, `history.pushState` с `popstate` и заход по прямой ссылке поднимают
|
|
75
|
+
приложение заново, и накопленного состояния — открытого аутлета, гарда прошлой панели — у него
|
|
76
|
+
нет. Чистый проход по адресам читается как «дефект не подтверждается»: панель ленты событий
|
|
77
|
+
застревала на первом же нажатии из меню и трижды прошла проверку адресами.
|
|
78
|
+
|
|
79
|
+
## Частые промахи
|
|
80
|
+
|
|
81
|
+
- Комментарий в конфиге — гипотеза наравне с прочими. Утверждение о вытеснении записи кэша
|
|
82
|
+
продержалось три круга состязательного разбора кода и было опровергнуто одним `curl`.
|
|
83
|
+
- Вывод «дефекта нет, это кэш» закрывает разбор, поэтому принимается только после проверки на
|
|
84
|
+
чистой сборке.
|
|
85
|
+
- Дефект в клиентском чанке от компиляции до правки выглядит как дефект кода: признак
|
|
86
|
+
дев-сборки — имена бандла без хеша.
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: browser-verification-stand
|
|
3
|
+
kind: pattern
|
|
4
|
+
rule: browser-verification
|
|
5
|
+
description: Паттерн правила browser-verification. Брать, когда нужен честный стенд — прод-сборка сайта, стенд админки, стенд под настоящим nginx, вход в админку, разбор того, что висит на порту. Не брать для замеров вёрстки — это паттерн browser-verification-measure.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Честный стенд
|
|
9
|
+
|
|
10
|
+
Паттерн правила `browser-verification`. Что при этом должно быть верно — закон
|
|
11
|
+
`docs/constitution/verifiability.md`.
|
|
12
|
+
|
|
13
|
+
## Когда брать
|
|
14
|
+
|
|
15
|
+
- Проверяется то, чего на дев-сервере не видно: разметка от сервера, локали, кэш,
|
|
16
|
+
перенаправления, заголовки, размер бандла.
|
|
17
|
+
- Порт отвечает не тем, чего ждали.
|
|
18
|
+
- Нужен вход в админку.
|
|
19
|
+
|
|
20
|
+
## Сначала — что отвечает на порту
|
|
21
|
+
|
|
22
|
+
До первого запроса, а не после непонятного ответа:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
lsof -nP -iTCP:{{apiPort}} -sTCP:LISTEN
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
На {{apiPort}} регулярно висит собранный артефакт из прошлой сессии
|
|
29
|
+
(`node -r dotenv/config dist/apps/api/main.js`): он отвечает 200 старым кодом, а процедуры,
|
|
30
|
+
заведённой в ветке, у него нет вовсе. Таких процессов бывает несколько, и снимать надо все —
|
|
31
|
+
по PID из `lsof`, каждый: `pkill` по шаблону `nx serve api` не попадает ни в один.
|
|
32
|
+
|
|
33
|
+
## Прод-сборка сайта
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npx nx build site
|
|
37
|
+
PORT={{prodSitePort}} node dist/apps/site/server/server.mjs
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Это не дев-сервер: гард ловит `nx|ng serve`, пакетные раннеры и статические серверы, а запуск
|
|
41
|
+
собранного сервера пропускает.
|
|
42
|
+
|
|
43
|
+
## Стенд админки
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npx nx build admin --base-href=/
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Без `--base-href` стенд отдаёт пустую страницу без ошибок в консоли. Стенду, на котором нужен
|
|
50
|
+
Angular DevTools, нужна ещё и dev-конфигурация (`--configuration=development`): прод-сборка не
|
|
51
|
+
публикует `window.ng`. Выводы о размере бандла и минификации на такой сборке делать нельзя.
|
|
52
|
+
|
|
53
|
+
Сессия кладётся в `localStorage['vm.admin.token']` **строкой JSON** (`JSON.stringify(token)`),
|
|
54
|
+
иначе приложение её не прочитает. Токен не подписывается руками, а берётся у живого API:
|
|
55
|
+
`POST /<область>.v1.AuthService/Login`. Команду с паролем классификатор блокирует — обходить не
|
|
56
|
+
надо, спрашивать разрешение у владельца.
|
|
57
|
+
|
|
58
|
+
Взять уже открытую сессию нельзя: чтение `localStorage['vm.admin.token']` из браузера
|
|
59
|
+
блокируется. Оба пути к своему стенду упираются в пароль, поэтому остаётся третий — смотреть
|
|
60
|
+
на админке владельца, где вход уже сделан. Свой стенд нужен, только когда проверяют
|
|
61
|
+
прод-сборку, `--base-href` или конфиг nginx; чтобы просто посмотреть экраны, он не нужен.
|
|
62
|
+
|
|
63
|
+
## Стенд API
|
|
64
|
+
|
|
65
|
+
Собранный артефакт поднимается на свободном порту, а не на {{apiPort}}: на {{apiPort}} отвечает API
|
|
66
|
+
владельца, и окружение у него не то, которое проверяется.
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npx nx build api
|
|
70
|
+
env -u JWT_SECRET NODE_ENV=production API_PORT={{prodApiPort}} DATABASE_URL=… node dist/apps/api/main.js
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Переменные окружения задаются в самой команде, по одной на проверяемый случай. Отказ на
|
|
74
|
+
старте — такой же результат проверки, как ответ 200: приложение, упавшее в фабрике
|
|
75
|
+
провайдера, порт не слушает вовсе, и это видно по `lsof`, а не по тексту в консоли.
|
|
76
|
+
|
|
77
|
+
Процедура зовётся полным именем, как её объявляет контракт:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
curl -sS -X POST http://localhost:{{prodApiPort}}/<область>.v1.AuthService/GetMe \
|
|
81
|
+
-H 'content-type: application/json' -H "authorization: Bearer $TOKEN" -d '{}'
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Токен подписывается руками только там, где проверяется сама подпись: доказать, что отладочный
|
|
85
|
+
ключ принимается, другого пути не имеет. Во всех остальных случаях токен берётся у живого API
|
|
86
|
+
входом — рукописный скрывает расхождение состава притязаний.
|
|
87
|
+
|
|
88
|
+
## Стенд под настоящим nginx
|
|
89
|
+
|
|
90
|
+
Кэш, перенаправления и заголовки живут в `deploy/nginx.conf`, а не в приложении. Любой вывод
|
|
91
|
+
про `301`, `Cache-Control` и `X-Cache-Status` делается только здесь:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
docker run -d --name <префикс>-stand-nginx \
|
|
95
|
+
--add-host api:host-gateway --add-host ssr:host-gateway \
|
|
96
|
+
-p {{dockerSitePort}}:80 -p {{dockerAdminPort}}:8081 \
|
|
97
|
+
-v "$PWD/deploy/nginx/main.conf:/etc/nginx/nginx.conf:ro" \
|
|
98
|
+
-v "$PWD/deploy:/etc/nginx/conf.d:ro" \
|
|
99
|
+
-v "$PWD/dist/apps/admin/browser:/usr/share/nginx/html/admin:ro" \
|
|
100
|
+
nginx:1.27-alpine
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
- Конфиг монтируется **каталогом**, а не одиночным файлом: редактор пересоздаёт файл,
|
|
104
|
+
контейнеру остаётся обрезанная копия, и `nginx -t` внутри падает на «unexpected end of file»
|
|
105
|
+
при целом файле снаружи. Лечится пересозданием контейнера, а не правкой конфига.
|
|
106
|
+
- Главный конфиг подставляется отдельной строкой: он лежит в `deploy/nginx/`, а не рядом с
|
|
107
|
+
`nginx.conf`, — файл с расширением `.conf` в смонтированном каталоге попал бы в `include`
|
|
108
|
+
и уронил бы nginx на директиве `user`. Без него стенд поднимается на конфиге образа, и
|
|
109
|
+
предел соединений на воркер там свой.
|
|
110
|
+
- Сервер отдачи страниц отвечает `400` на чужой `Host`: все запросы идут с
|
|
111
|
+
`-H "Host: localhost"`.
|
|
112
|
+
- Переменные окружения стенда обязаны смотреть на процессы стенда. `CACHE_REFRESH_URL`,
|
|
113
|
+
направленный на {{sitePort}}, сбрасывает кэш мимо того процесса, который держит справочник
|
|
114
|
+
перенаправлений в памяти, — исправный механизм при этом выглядит сломанным.
|
|
115
|
+
|
|
116
|
+
## Дерево для сравнения
|
|
117
|
+
|
|
118
|
+
Сказать «это сломала правка» можно, только если видно, что до правки было иначе. Проверяют это
|
|
119
|
+
вторым деревом, а не по памяти:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
git worktree add ../<префикс>-base <коммит> # ветка задачи не трогается
|
|
123
|
+
pnpm install --frozen-lockfile # из ../<префикс>-base: node_modules у него свои
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
- Для сравнения берут не главную ветку, а последний коммит, на котором дерево собирается:
|
|
127
|
+
главная бывает сломана, и тогда «до» и «после» различаются не из-за правки. Собирается ли
|
|
128
|
+
коммит — проверяют сборкой, а не тем, что он в главной ветке.
|
|
129
|
+
- Стенд второго дерева поднимают на своих портах: {{sitePort}}, {{adminPort}} и {{apiPort}} заняты владельцем, а {{ssrPort}}
|
|
130
|
+
занимать нельзя — стенд разработчика ходит по имени `ssr:{{ssrPort}}`.
|
|
131
|
+
- Оба стенда держат поднятыми одновременно: если сравнивать по памяти между двумя запусками,
|
|
132
|
+
заметишь только то, что успел запомнить.
|
|
133
|
+
|
|
134
|
+
## Частые промахи
|
|
135
|
+
|
|
136
|
+
- Свой дев-сервер не поднимать: сайт на {{sitePort}}, админка на {{adminPort}}, API на {{apiPort}} уже подняты
|
|
137
|
+
владельцем, и второй экземпляр отбивается гардом.
|
|
138
|
+
- **Общая сборка глушит все три дев-сервера владельца, а не только API.** После
|
|
139
|
+
`nx run-many -t build` ложатся и сайт на {{sitePort}}, и админка на {{adminPort}}. Собирать надо то, что
|
|
140
|
+
проверяешь (`npx nx build site`), а не всё дерево. Если серверы легли, поднять их обратно
|
|
141
|
+
агент не может — мешает гард, поэтому владельцу говорят об этом сразу, а не в конце сессии.
|
|
142
|
+
- Порт {{ssrPort}} занимать осторожно: стенд разработчика на {{sitePort}} ходит по тому же имени `ssr:{{ssrPort}}`
|
|
143
|
+
через `host-gateway`, и пока на нём висит чужой процесс, стенд отдаёт чужую сборку.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: component-structure-new
|
|
3
|
+
kind: pattern
|
|
4
|
+
rule: component-structure
|
|
5
|
+
description: Паттерн правила component-structure. Брать при заведении или правке *.component.ts — готовый декоратор с порядком свойств, группировка импортов, раскладка полей класса, договорённости шаблона и qa-dataid. Не брать для состояния и потоков — это паттерн angular-patterns-state.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Файл компонента
|
|
9
|
+
|
|
10
|
+
Паттерн правила `component-structure`. Что при этом должно быть верно — закон
|
|
11
|
+
`docs/constitution/frontend-application.md`.
|
|
12
|
+
|
|
13
|
+
## Когда брать
|
|
14
|
+
|
|
15
|
+
- Заводится новый компонент.
|
|
16
|
+
- Правится декоратор, список импортов или шаблон существующего.
|
|
17
|
+
|
|
18
|
+
## Декоратор: порядок свойств
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
@Component({
|
|
22
|
+
selector: '<префикс>-component-name', // 1. селектор
|
|
23
|
+
templateUrl: './component-name.component.html', // 2. шаблон
|
|
24
|
+
styleUrl: './component-name.component.scss', // 3. стиль, в единственном числе
|
|
25
|
+
changeDetection: ChangeDetectionStrategy.OnPush, // 4. стратегия перерисовки
|
|
26
|
+
imports: [
|
|
27
|
+
// 5. импорты, группами
|
|
28
|
+
// angular
|
|
29
|
+
FormsModule,
|
|
30
|
+
|
|
31
|
+
// rt-tools
|
|
32
|
+
BlockDirective,
|
|
33
|
+
ElemDirective,
|
|
34
|
+
|
|
35
|
+
// components
|
|
36
|
+
SomeChildComponent,
|
|
37
|
+
],
|
|
38
|
+
providers: [], // 6. провайдеры
|
|
39
|
+
host: { class: '<префикс>-component-name' }, // 7. привязки хоста
|
|
40
|
+
})
|
|
41
|
+
export class ComponentNameComponent {
|
|
42
|
+
readonly #someService: SomeService = inject(SomeService);
|
|
43
|
+
|
|
44
|
+
public readonly data: InputSignal<Item[]> = input.required<Item[]>();
|
|
45
|
+
public readonly save: OutputEmitterRef<void> = output<void>();
|
|
46
|
+
|
|
47
|
+
protected readonly myButton: Signal<ElementRef | undefined> = viewChild<ElementRef>('button');
|
|
48
|
+
protected readonly items: WritableSignal<Item[]> = signal<Item[]>([]);
|
|
49
|
+
protected readonly itemCount: Signal<number> = computed((): number => this.items().length);
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Группирующие комментарии в `imports` обязательны: `// angular`, `// rt-tools`,
|
|
54
|
+
`// components`, `// directives`, `// pipes`.
|
|
55
|
+
|
|
56
|
+
## Шаблон
|
|
57
|
+
|
|
58
|
+
- Самозакрывающиеся теги у компонентов без содержимого: `<<префикс>-gallery />`.
|
|
59
|
+
- Лишних обёрток нет — корнем работает `:host`, класс блока приходит с `host: { class: … }`.
|
|
60
|
+
- Сложный шаблон объявляет блок `<ng-container rtBlock="component-name">` в корне.
|
|
61
|
+
- Один компонент в обеих ветках `@if` — это условная привязка:
|
|
62
|
+
|
|
63
|
+
```html
|
|
64
|
+
<!-- ✗ -->
|
|
65
|
+
@if (isRangeMode()) { <<префикс>-calendar [rangeMode]="true" /> } @else { <<префикс>-calendar [rangeMode]="false" /> }
|
|
66
|
+
|
|
67
|
+
<!-- ✓ -->
|
|
68
|
+
<<префикс>-calendar [rangeMode]="isRangeMode()" />
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
- Прокрутка по документу — через роутер, а не `href="#id"`:
|
|
72
|
+
|
|
73
|
+
```html
|
|
74
|
+
<a fragment="booking" [routerLink]="[]">…</a>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## `qa-dataid` — на каждый интерактивный элемент
|
|
78
|
+
|
|
79
|
+
```html
|
|
80
|
+
<button vmButton qa-dataid="calendar-retry-prices" type="button" (click)="retryPrices.emit()">Повторить</button>
|
|
81
|
+
<div rtElem="grid" qa-dataid="admin-calendar-grid"></div>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
- Значение — kebab-case по смыслу элемента, без имени компонента-обёртки: `calendar-day`,
|
|
85
|
+
`booking-submit`.
|
|
86
|
+
- Уникальность — в пределах экрана; повторяющиеся элементы списка носят один якорь и
|
|
87
|
+
различаются через `data-*` (`[attr.data-iso]`, `[attr.data-state]`).
|
|
88
|
+
- Декоративный элемент помечается `qa-skip` на самом теге.
|
|
89
|
+
|
|
90
|
+
## Частые промахи
|
|
91
|
+
|
|
92
|
+
- `selector: 'app-component'` вместо `<префикс>-component`.
|
|
93
|
+
- `styleUrls: ['./component.scss']` вместо `styleUrl` — множественное число здесь не то.
|
|
94
|
+
- Вызов метода в биндинге: `{{ getTotal() }}` отбивается линтером. Замена — `computed()`, а
|
|
95
|
+
там, где значение приходит из контекста шаблона, — чистый пайп.
|
|
96
|
+
- Обёртка, которая существует только чтобы быть flex- или grid-контейнером вокруг всех детей:
|
|
97
|
+
её раскладка уезжает на `:host`.
|
|
98
|
+
- Глубокий относительный импорт между либами вместо `@<область>/<семья>/<домен>/<слой>`.
|
|
99
|
+
- Своя разметка вместо готового компонента кита — правило `reuse-first`.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dependencies-upgrade
|
|
3
|
+
kind: pattern
|
|
4
|
+
rule: dependencies
|
|
5
|
+
description: Паттерн правила dependencies. Брать при подъёме версий пакетов — выбор верхней границы по peer-диапазонам, порядок проверок, граница переформатирования после обновления форматтера, разбор новых правил линтера. Не брать для ветки, коммита и PR — это паттерн git-workflow-commit.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Подъём версий
|
|
9
|
+
|
|
10
|
+
Паттерн правила `dependencies`. Что при этом должно быть верно — закон
|
|
11
|
+
`docs/constitution/delivery.md`.
|
|
12
|
+
|
|
13
|
+
## Когда брать
|
|
14
|
+
|
|
15
|
+
- Поднимается версия любого пакета.
|
|
16
|
+
- Пришло предупреждение об уязвимости в транзитивной зависимости.
|
|
17
|
+
- После установки посыпались замечания линтера или переформатировалось дерево.
|
|
18
|
+
|
|
19
|
+
## Верхнюю границу задают peer-диапазоны
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm view @angular/compiler-cli@22.1.0 peerDependencies
|
|
23
|
+
npm view typescript versions --json | tail -5
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Пакет поднимают до последней версии, которая ещё попадает в peer-диапазон каждого, кто от него
|
|
27
|
+
зависит, а не до последней в реестре. Проверять приходится по одному пакету: `autoInstallPeers:
|
|
28
|
+
true` молча доставляет недостающее, и конфликт peer-диапазонов установку не останавливает.
|
|
29
|
+
|
|
30
|
+
## Порядок
|
|
31
|
+
|
|
32
|
+
1. `package.json` — точные номера, ни одного `^` и `~`.
|
|
33
|
+
2. `pnpm install`; снимок едет тем же коммитом.
|
|
34
|
+
3. `npx nx run-many -t build -p site admin api` — сборка **до** линтеров: о несовместимости
|
|
35
|
+
типов она говорит одной строкой, а линтер на том же дереве даёт сотню замечаний, и
|
|
36
|
+
настоящая причина в них теряется.
|
|
37
|
+
4. `npm run lint` и `npm run stylelint`.
|
|
38
|
+
5. Тесты — правило `testing`. После смены версии Playwright сначала
|
|
39
|
+
`npx playwright install chromium`.
|
|
40
|
+
6. Обход экранов в браузере — правило `browser-verification`: если пакет рисует вёрстку, смену
|
|
41
|
+
его версии тесты не покрывают.
|
|
42
|
+
|
|
43
|
+
## Переформатирование
|
|
44
|
+
|
|
45
|
+
Обновлённый форматтер меняет все файлы дерева. Что из этого оставлять, решает не размер дифа,
|
|
46
|
+
а то, чьё форматирование проверяет линтер: `.ts` и `.html` — `eslint.config.mjs`, `.scss` —
|
|
47
|
+
`stylelint.config.js`. Остальное переформатируется потом, вместе с правкой, которая эти файлы
|
|
48
|
+
и так трогает: полный прогон изменил 883 файла, из которых проверяются 85, и осмысленные
|
|
49
|
+
правки в такой куче не разглядеть.
|
|
50
|
+
|
|
51
|
+
## Новые правила линтера
|
|
52
|
+
|
|
53
|
+
Правило, которое запрещает приём, принятый в этом дереве, выключают один раз в конфиге и
|
|
54
|
+
пишут рядом причину. Правило, которое право по существу, обходят точечным
|
|
55
|
+
`eslint-disable-next-line` — тоже с причиной. Смешивать нельзя: выключенное в конфиге без
|
|
56
|
+
причины через месяц выглядит забытым, а шестьдесят пять точечных обходов одного правила —
|
|
57
|
+
списком того, что надо было выключить один раз.
|
|
58
|
+
|
|
59
|
+
## Частые промахи
|
|
60
|
+
|
|
61
|
+
- `pnpm install` не двигает пакет, если он назван в `overrides`: строка из списка держит
|
|
62
|
+
прежнюю версию, а установка проходит без ошибок.
|
|
63
|
+
- Линтеры до сборки — лишний круг: разбираешь замечания, которых после сборки не будет.
|
|
64
|
+
- Обновление, попавшее в ветку с другой задачей, откатится только вместе с ней: у обновления
|
|
65
|
+
зависимостей своя задача и своя ветка.
|