@nitra/cursor 1.8.71 → 1.8.74
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/mdc/abie.mdc +20 -16
- package/mdc/k8s.mdc +14 -0
- package/package.json +1 -1
- package/scripts/check-abie.mjs +72 -77
- package/scripts/check-bun.mjs +4 -7
- package/scripts/check-docker.mjs +5 -8
- package/scripts/check-ga.mjs +5 -8
- package/scripts/check-js-format.mjs +4 -7
- package/scripts/check-js-lint.mjs +4 -7
- package/scripts/check-js-pino.mjs +10 -12
- package/scripts/check-k8s.mjs +5 -8
- package/scripts/check-nginx-default-tpl.mjs +5 -8
- package/scripts/check-npm-module.mjs +4 -7
- package/scripts/check-style-lint.mjs +4 -7
- package/scripts/check-text.mjs +4 -7
- package/scripts/check-vue.mjs +17 -19
- package/scripts/run-docker.mjs +5 -8
- package/scripts/utils/check-reporter.mjs +27 -0
package/mdc/abie.mdc
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Правила для проєктів AbInBev Efes
|
|
3
3
|
alwaysApply: true
|
|
4
|
-
version: '1.
|
|
4
|
+
version: '1.7'
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
Правило **abie** для споживачів **@nitra/cursor**: **k8s** (
|
|
7
|
+
Правило **abie** для споживачів **@nitra/cursor**: **k8s** (Deployment + **HealthCheckPolicy** у **`hc.yaml`**, overlay **ua** / **ru** — **nodeSelector**, **HTTPRoute** (будь-який непорожній **`target.name`**), видалення **HealthCheckPolicy** у **ru**), а також гілки **dev**, **ua**, **ru** у **clean-merged-branch**.
|
|
8
|
+
|
|
9
|
+
**`npx @nitra/cursor check abie`** виконується лише якщо в **`.n-cursor.json`** у **`rules`** є **`abie`** — інакше вихід **0** без зауважень.
|
|
10
|
+
|
|
11
|
+
**Канон перевірки** — **`npm/scripts/check-abie.mjs`**: верхній JSDoc і реалізація задають точні умови, допустимі домени для hostnames, тексти помилок. Нижче — зміст правила й орієнтовні фрагменти YAML; не дублюй тут покроковий алгоритм зі скрипта.
|
|
8
12
|
|
|
9
13
|
## k8s: `hc.yaml` поруч із Deployment
|
|
10
14
|
|
|
11
|
-
Якщо під **`k8s`** є
|
|
15
|
+
Якщо під **`k8s`** є **Deployment**, у **тій самій директорії** має бути **`hc.yaml`** з **HealthCheckPolicy** (**`networking.gke.io/v1`**): коректний modeline **`$schema`**, **`/healthz`**, порт **8080**, **`targetRef.name`** = **`metadata.name`**.
|
|
12
16
|
|
|
13
17
|
```yaml title="hc.yaml"
|
|
14
18
|
# yaml-language-server: $schema=https://datreeio.github.io/CRDs-catalog/networking.gke.io/healthcheckpolicy_v1.json
|
|
@@ -30,19 +34,19 @@ spec:
|
|
|
30
34
|
name: СЕРВІС
|
|
31
35
|
```
|
|
32
36
|
|
|
33
|
-
## k8s: overlay **HTTPRoute**
|
|
37
|
+
## k8s: overlay **HTTPRoute** (**ua** / **ru**)
|
|
34
38
|
|
|
35
|
-
|
|
39
|
+
За наявності **Deployment** під **k8s** у **кожному** **`ua/kustomization.yaml`** та **`ru/kustomization.yaml`** потрібні **inline JSON6902** у **`patches`**: **target** **`kind: HTTPRoute`**, **непорожній `name`** (як у маніфесті маршруту). Мають бути зміни **`/spec/hostnames`** (домени abie — у скрипті) та **`/spec/parentRefs/0/namespace`** (**`ua`** / **`ru`**). Для **ru** — анотація **`gwin.yandex.cloud/rules.http.upgradeTypes: websocket`**. Як обирати **`op`** (**add** / **replace** тощо) у patch — **k8s.mdc** (розділ про JSON patch у kustomization).
|
|
36
40
|
|
|
37
41
|
```yaml title="…/ua/kustomization.yaml (фрагмент)"
|
|
38
42
|
- target:
|
|
39
43
|
kind: HTTPRoute
|
|
40
|
-
name:
|
|
44
|
+
name: my-httproute
|
|
41
45
|
patch: |-
|
|
42
46
|
- op: replace
|
|
43
47
|
path: /spec/hostnames
|
|
44
48
|
value:
|
|
45
|
-
- "abie.app" #
|
|
49
|
+
- "abie.app" # зокрема vybeerai.com.ua, *.vybeerai.com.ua, *.abie.app
|
|
46
50
|
- op: replace
|
|
47
51
|
path: /spec/parentRefs/0/namespace
|
|
48
52
|
value: ua
|
|
@@ -51,12 +55,12 @@ spec:
|
|
|
51
55
|
```yaml title="…/ru/kustomization.yaml (фрагмент)"
|
|
52
56
|
- target:
|
|
53
57
|
kind: HTTPRoute
|
|
54
|
-
name:
|
|
58
|
+
name: my-httproute
|
|
55
59
|
patch: |-
|
|
56
60
|
- op: replace
|
|
57
61
|
path: /spec/hostnames
|
|
58
62
|
value:
|
|
59
|
-
- "napitkivmeste.tech" #
|
|
63
|
+
- "napitkivmeste.tech" # зокрема выбирайонлайн.рф, *.napitkivmeste.tech, *.выбирайонлайн.рф
|
|
60
64
|
- op: replace
|
|
61
65
|
path: /spec/parentRefs/0/namespace
|
|
62
66
|
value: ru
|
|
@@ -65,7 +69,7 @@ spec:
|
|
|
65
69
|
```yaml title="…/ru/kustomization.yaml (фрагмент)"
|
|
66
70
|
- target:
|
|
67
71
|
kind: HTTPRoute
|
|
68
|
-
name:
|
|
72
|
+
name: my-httproute
|
|
69
73
|
patch: |-
|
|
70
74
|
- op: add
|
|
71
75
|
path: /metadata/annotations
|
|
@@ -73,9 +77,9 @@ spec:
|
|
|
73
77
|
gwin.yandex.cloud/rules.http.upgradeTypes: "websocket"
|
|
74
78
|
```
|
|
75
79
|
|
|
76
|
-
## k8s: overlay
|
|
80
|
+
## k8s: overlay **ru** і HealthCheckPolicy
|
|
77
81
|
|
|
78
|
-
Якщо в дереві **k8s** є **HealthCheckPolicy**,
|
|
82
|
+
Якщо в дереві **k8s** є **HealthCheckPolicy**, у **`ru/kustomization.yaml`** має бути patch **`$patch: delete`** для політики (узгоджено з **k8s.mdc**; перевірка в **`check-k8s.mjs`**, **`ruKustomizationHasHealthCheckDeletePatch`**). Підстав реальне ім’я замість **`СЕРВІС`**:
|
|
79
83
|
|
|
80
84
|
```yaml title="…/ru/kustomization.yaml (фрагмент)"
|
|
81
85
|
patches:
|
|
@@ -89,9 +93,9 @@ patches:
|
|
|
89
93
|
$patch: delete
|
|
90
94
|
```
|
|
91
95
|
|
|
92
|
-
## k8s: overlay
|
|
96
|
+
## k8s: overlay **ua** / **ru** і nodeSelector
|
|
93
97
|
|
|
94
|
-
|
|
98
|
+
За наявності **Deployment** під **k8s** у **кожному** **`…/ua/kustomization.yaml`** та **`…/ru/kustomization.yaml`** — patch на **`kind: Deployment`**: **ua** — **`spec.template.spec.nodeSelector`** з **`preem: false`**; **ru** — **`spec.template.spec.nodeSelector`** з **`yandex.cloud/preemptible: false`**. Форму **JSON6902** (шлях **`/spec/template/spec/nodeSelector`**, **`op`**) див. **k8s.mdc**.
|
|
95
99
|
|
|
96
100
|
```yaml title="…/ua/kustomization.yaml (фрагмент)"
|
|
97
101
|
patches:
|
|
@@ -119,7 +123,7 @@ patches:
|
|
|
119
123
|
|
|
120
124
|
### Базовий Deployment (`…/base/`)
|
|
121
125
|
|
|
122
|
-
Якщо **Deployment** у YAML під **`k8s`** лежить у шляху з сегментом **`base
|
|
126
|
+
Якщо **Deployment** у YAML під **`k8s`** лежить у шляху з сегментом **`base`**, у **`spec.template.spec.nodeSelector`** має бути **`preem`** зі значенням **істинно** (**`true`** або рядок **`'true'`**); overlay **ua** / **ru** підміняє селектор.
|
|
123
127
|
|
|
124
128
|
```yaml title="…/base/deploy.yaml (фрагмент)"
|
|
125
129
|
spec:
|
|
@@ -131,7 +135,7 @@ spec:
|
|
|
131
135
|
|
|
132
136
|
## Git branches
|
|
133
137
|
|
|
134
|
-
У **`.github/workflows/clean-merged-branch.yml`** у кроці **`phpdocker-io/github-actions-delete-abandoned-branches`** значення **`with.ignore_branches`** має містити **dev**, **ua** та **ru** (разом з іншими гілками, якщо потрібно)
|
|
138
|
+
У **`.github/workflows/clean-merged-branch.yml`** у кроці **`phpdocker-io/github-actions-delete-abandoned-branches`** значення **`with.ignore_branches`** має містити **dev**, **ua** та **ru** (разом з іншими гілками, якщо потрібно):
|
|
135
139
|
|
|
136
140
|
```yaml title=".github/workflows/clean-merged-branch.yml (фрагмент)"
|
|
137
141
|
with:
|
package/mdc/k8s.mdc
CHANGED
|
@@ -206,6 +206,20 @@ patches:
|
|
|
206
206
|
value: dev
|
|
207
207
|
```
|
|
208
208
|
|
|
209
|
+
4. **JSON patch у kustomization:** де можливо, змінюй ресурс через **`op: replace`** (одна операція на `path`), а не пару **`remove` + `add`** на той самий шлях. **`add`** / **`remove`** лишай лише коли **`replace`** не підходить (наприклад додати новий ключ або прибрати поле без заміни).
|
|
210
|
+
|
|
211
|
+
```yaml title="overlay/kustomization.yaml (фрагмент)"
|
|
212
|
+
patches:
|
|
213
|
+
- target:
|
|
214
|
+
kind: Deployment
|
|
215
|
+
name: x
|
|
216
|
+
patch: |-
|
|
217
|
+
- op: replace
|
|
218
|
+
path: /spec/template/spec/nodeSelector
|
|
219
|
+
value:
|
|
220
|
+
preem: "false"
|
|
221
|
+
```
|
|
222
|
+
|
|
209
223
|
## Перевірка
|
|
210
224
|
|
|
211
225
|
**`npx @nitra/cursor check k8s`** — програмні критерії в **JSDoc на початку** **`npm/scripts/check-k8s.mjs`**. Якщо під **`k8s`** немає **`*.yaml`** — крок пропущено. Канон **`$schema`** для редактора — розділ **«Визначення схеми YAML`** нижче.
|
package/package.json
CHANGED
package/scripts/check-abie.mjs
CHANGED
|
@@ -21,9 +21,10 @@
|
|
|
21
21
|
* має бути inline **JSON6902** patch на **`kind: Deployment`**: для **ua** — **`op: add`**, **`path: /spec/template/spec/nodeSelector`**,
|
|
22
22
|
* **`preem: false`**; для **ru** — **`op: replace`**, той самий **path**, **`yandex.cloud/preemptible: false`** (див. abie.mdc).
|
|
23
23
|
*
|
|
24
|
-
* **HTTPRoute
|
|
25
|
-
* inline **JSON6902** на **`kind: HTTPRoute
|
|
26
|
-
* **replace** **`/spec/parentRefs/0/namespace`** (**ua** / **ru**); для **ru** також
|
|
24
|
+
* **HTTPRoute (overlay):** за тієї ж умови (**Deployment** під **k8s**) у **кожному** **`ua`/`ru` kustomization** має бути
|
|
25
|
+
* inline **JSON6902** на **`kind: HTTPRoute`** з **непорожнім `target.name`** (будь-яке ім’я): **replace** **`/spec/hostnames`**
|
|
26
|
+
* (домени з abie.mdc), **replace** **`/spec/parentRefs/0/namespace`** (**ua** / **ru**); для **ru** також
|
|
27
|
+
* **`gwin.yandex.cloud/rules.http.upgradeTypes: websocket`**.
|
|
27
28
|
*/
|
|
28
29
|
import { existsSync } from 'node:fs'
|
|
29
30
|
import { readFile } from 'node:fs/promises'
|
|
@@ -32,7 +33,7 @@ import { dirname, join, relative } from 'node:path'
|
|
|
32
33
|
import { parseAllDocuments } from 'yaml'
|
|
33
34
|
|
|
34
35
|
import { pathHasK8sSegment, ruKustomizationHasHealthCheckDeletePatch } from './check-k8s.mjs'
|
|
35
|
-
import {
|
|
36
|
+
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
36
37
|
import { flattenWorkflowSteps, getStepUses, parseWorkflowYaml } from './utils/gha-workflow.mjs'
|
|
37
38
|
import { walkDir } from './utils/walkDir.mjs'
|
|
38
39
|
|
|
@@ -79,9 +80,9 @@ export function isAbieK8sBaseYamlPath(rel) {
|
|
|
79
80
|
/**
|
|
80
81
|
* Чи значення **`preem`** у base **Deployment** вважається «істинним» за abie.mdc (**true** або рядок **`true`** без урахування регістру).
|
|
81
82
|
* @param {unknown} v значення з YAML
|
|
82
|
-
* @returns {boolean}
|
|
83
|
+
* @returns {boolean} **true**, якщо значення вважається істинним за abie.mdc
|
|
83
84
|
*/
|
|
84
|
-
function
|
|
85
|
+
function isAbiePreemTruthy(v) {
|
|
85
86
|
if (v === true) {
|
|
86
87
|
return true
|
|
87
88
|
}
|
|
@@ -117,7 +118,7 @@ export function deploymentDocumentHasAbieBasePreemNodeSelector(obj) {
|
|
|
117
118
|
if (nodeSelector === null || typeof nodeSelector !== 'object' || Array.isArray(nodeSelector)) {
|
|
118
119
|
return false
|
|
119
120
|
}
|
|
120
|
-
return
|
|
121
|
+
return isAbiePreemTruthy(nodeSelector.preem)
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
/**
|
|
@@ -279,9 +280,10 @@ async function collectDeploymentDirs(root, yamlAbs, fail) {
|
|
|
279
280
|
* @param {string} root корінь репозиторію
|
|
280
281
|
* @param {string[]} yamlFilesAbs yaml під k8s
|
|
281
282
|
* @param {(msg: string) => void} fail callback
|
|
283
|
+
* @param {(msg: string) => void} passFn успішне повідомлення
|
|
282
284
|
* @returns {Promise<void>}
|
|
283
285
|
*/
|
|
284
|
-
async function ensureAbieBaseDeploymentPreemNodeSelector(root, yamlFilesAbs, fail) {
|
|
286
|
+
async function ensureAbieBaseDeploymentPreemNodeSelector(root, yamlFilesAbs, fail, passFn) {
|
|
285
287
|
const baseFiles = yamlFilesAbs.filter(abs => {
|
|
286
288
|
const rel = relative(root, abs).replaceAll('\\', '/') || abs
|
|
287
289
|
return isAbieK8sBaseYamlPath(rel)
|
|
@@ -311,24 +313,24 @@ async function ensureAbieBaseDeploymentPreemNodeSelector(root, yamlFilesAbs, fai
|
|
|
311
313
|
return
|
|
312
314
|
}
|
|
313
315
|
for (const doc of docs) {
|
|
314
|
-
if (doc.errors.length
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
316
|
+
if (doc.errors.length === 0) {
|
|
317
|
+
const obj = doc.toJSON()
|
|
318
|
+
if (isDeploymentDoc(obj)) {
|
|
319
|
+
anyBaseDeployment = true
|
|
320
|
+
if (!deploymentDocumentHasAbieBasePreemNodeSelector(obj)) {
|
|
321
|
+
fail(
|
|
322
|
+
`${rel}: Deployment у base: потрібен spec.template.spec.nodeSelector.preem: true (або 'true') — abie.mdc`
|
|
323
|
+
)
|
|
324
|
+
return
|
|
325
|
+
}
|
|
326
|
+
}
|
|
325
327
|
}
|
|
326
328
|
}
|
|
327
329
|
}
|
|
328
330
|
if (anyBaseDeployment) {
|
|
329
|
-
|
|
331
|
+
passFn('Deployment у …/base/…: nodeSelector.preem відповідає abie.mdc')
|
|
330
332
|
} else {
|
|
331
|
-
|
|
333
|
+
passFn('Немає Deployment у шляхах …/base/… — перевірку preem у base пропущено')
|
|
332
334
|
}
|
|
333
335
|
}
|
|
334
336
|
|
|
@@ -483,11 +485,11 @@ const ABIE_RU_HTTPROUTE_HOST_MARKERS = [
|
|
|
483
485
|
]
|
|
484
486
|
|
|
485
487
|
/**
|
|
486
|
-
* Збирає тексти inline **patch** для **HTTPRoute
|
|
488
|
+
* Збирає тексти inline **patch** для **HTTPRoute** (будь-який непорожній **target.name**) з одного документа **Kustomization**.
|
|
487
489
|
* @param {import('yaml').Document} doc документ після **parseAllDocuments**
|
|
488
490
|
* @returns {string[]} непорожні рядки **patch**
|
|
489
491
|
*/
|
|
490
|
-
function
|
|
492
|
+
function collectAbieHttpRoutePatchStringsFromKustomizationDoc(doc) {
|
|
491
493
|
if (doc.errors.length > 0) {
|
|
492
494
|
return []
|
|
493
495
|
}
|
|
@@ -506,28 +508,25 @@ function collectNginxRunPatchStringsFromKustomizationDoc(doc) {
|
|
|
506
508
|
/** @type {string[]} */
|
|
507
509
|
const out = []
|
|
508
510
|
for (const p of patches) {
|
|
509
|
-
if (p
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
const patchStr = pr.patch
|
|
522
|
-
if (typeof patchStr === 'string' && patchStr.trim() !== '') {
|
|
523
|
-
out.push(patchStr)
|
|
511
|
+
if (p !== null && typeof p === 'object' && !Array.isArray(p)) {
|
|
512
|
+
const pr = /** @type {Record<string, unknown>} */ (p)
|
|
513
|
+
const target = pr.target
|
|
514
|
+
if (target !== null && typeof target === 'object' && !Array.isArray(target)) {
|
|
515
|
+
const tg = /** @type {Record<string, unknown>} */ (target)
|
|
516
|
+
if (tg.kind === 'HTTPRoute' && typeof tg.name === 'string' && tg.name.trim() !== '') {
|
|
517
|
+
const patchStr = pr.patch
|
|
518
|
+
if (typeof patchStr === 'string' && patchStr.trim() !== '') {
|
|
519
|
+
out.push(patchStr)
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
524
523
|
}
|
|
525
524
|
}
|
|
526
525
|
return out
|
|
527
526
|
}
|
|
528
527
|
|
|
529
528
|
/**
|
|
530
|
-
*
|
|
529
|
+
* Збирає всі inline **JSON6902**-фрагменти для **HTTPRoute** (непорожній **target.name**) у **kustomization.yaml** (усі документи у файлі).
|
|
531
530
|
* @param {string} raw повний текст файлу
|
|
532
531
|
* @returns {string} текст для **`validateAbieNginxRunHttpRoutePatches`** (може бути порожнім)
|
|
533
532
|
*/
|
|
@@ -546,47 +545,44 @@ export function getCombinedNginxRunPatchTextFromKustomization(raw) {
|
|
|
546
545
|
/** @type {string[]} */
|
|
547
546
|
const chunks = []
|
|
548
547
|
for (const doc of docs) {
|
|
549
|
-
chunks.push(...
|
|
548
|
+
chunks.push(...collectAbieHttpRoutePatchStringsFromKustomizationDoc(doc))
|
|
550
549
|
}
|
|
551
550
|
return chunks.join('\n')
|
|
552
551
|
}
|
|
553
552
|
|
|
554
553
|
/**
|
|
555
|
-
* Перевіряє
|
|
556
|
-
* @param {string} combined текст одного або кількох inline **patch
|
|
554
|
+
* Перевіряє сукупний текст patch(ів) **HTTPRoute** (будь-яке **target.name**) на відповідність abie.mdc.
|
|
555
|
+
* @param {string} combined текст одного або кількох inline **patch**, розділених символом нового рядка
|
|
557
556
|
* @param {'ua' | 'ru'} mode **ua** або **ru**
|
|
558
557
|
* @returns {string | null} повідомлення про помилку або **null**
|
|
559
558
|
*/
|
|
560
559
|
export function validateAbieNginxRunHttpRoutePatches(combined, mode) {
|
|
561
560
|
if (typeof combined !== 'string' || combined.trim() === '') {
|
|
562
|
-
return `очікується patch target kind HTTPRoute name
|
|
561
|
+
return `очікується patch target kind HTTPRoute з непорожнім target.name (replace hostnames, parentRefs namespace ${mode}; для ru — також gwin… upgradeTypes websocket) — abie.mdc`
|
|
563
562
|
}
|
|
564
563
|
const hasHostnamesReplace = /-\s*op:\s*replace\b[\s\S]{0,200}?path:\s*\/spec\/hostnames\b/m.test(combined)
|
|
565
564
|
if (!hasHostnamesReplace) {
|
|
566
|
-
return 'HTTPRoute
|
|
565
|
+
return 'HTTPRoute: потрібен блок op replace з path /spec/hostnames (abie.mdc)'
|
|
567
566
|
}
|
|
568
567
|
const markers = mode === 'ua' ? ABIE_UA_HTTPROUTE_HOST_MARKERS : ABIE_RU_HTTPROUTE_HOST_MARKERS
|
|
569
568
|
if (!markers.some(m => combined.includes(m))) {
|
|
570
|
-
return `HTTPRoute
|
|
569
|
+
return `HTTPRoute: у value для /spec/hostnames має бути один із доменів abie (${markers.join(', ')}) — abie.mdc`
|
|
571
570
|
}
|
|
572
|
-
const
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
)
|
|
577
|
-
|
|
578
|
-
return `HTTPRoute nginx-run: потрібен replace path /spec/parentRefs/0/namespace з value ${ns} (abie.mdc)`
|
|
571
|
+
const namespaceOk =
|
|
572
|
+
mode === 'ua'
|
|
573
|
+
? /path:\s*\/spec\/parentRefs\/0\/namespace\b[\s\S]{0,200}?value:\s*['"]?ua['"]?(?:\s|$)/mu.test(combined)
|
|
574
|
+
: /path:\s*\/spec\/parentRefs\/0\/namespace\b[\s\S]{0,200}?value:\s*['"]?ru['"]?(?:\s|$)/mu.test(combined)
|
|
575
|
+
if (!namespaceOk) {
|
|
576
|
+
return `HTTPRoute: потрібен replace path /spec/parentRefs/0/namespace з value ${mode} (abie.mdc)`
|
|
579
577
|
}
|
|
580
|
-
if (mode === 'ru') {
|
|
581
|
-
|
|
582
|
-
return 'HTTPRoute nginx-run (ru): потрібна анотація gwin.yandex.cloud/rules.http.upgradeTypes: websocket (abie.mdc)'
|
|
583
|
-
}
|
|
578
|
+
if (mode === 'ru' && !/gwin\.yandex\.cloud\/rules\.http\.upgradeTypes:\s*['"]?websocket['"]?/m.test(combined)) {
|
|
579
|
+
return 'HTTPRoute (ru): потрібна анотація gwin.yandex.cloud/rules.http.upgradeTypes: websocket (abie.mdc)'
|
|
584
580
|
}
|
|
585
581
|
return null
|
|
586
582
|
}
|
|
587
583
|
|
|
588
584
|
/**
|
|
589
|
-
* Чи **kustomization** містить валідні для abie
|
|
585
|
+
* Чи **kustomization** містить валідні для abie **patch** для **HTTPRoute** з непорожнім **target.name** (**ua** або **ru**).
|
|
590
586
|
* @param {string} raw повний текст **kustomization.yaml**
|
|
591
587
|
* @param {'ua' | 'ru'} mode overlay
|
|
592
588
|
* @returns {boolean} true, якщо **`validateAbieNginxRunHttpRoutePatches`** повертає **null**
|
|
@@ -780,9 +776,10 @@ async function ensureRuKustomizationHealthCheckDelete(root, yamlFilesAbs, health
|
|
|
780
776
|
* @param {string} root корінь репозиторію
|
|
781
777
|
* @param {string[]} yamlFilesAbs yaml під k8s
|
|
782
778
|
* @param {(msg: string) => void} fail callback
|
|
779
|
+
* @param {(msg: string) => void} passFn успішне повідомлення
|
|
783
780
|
* @returns {Promise<void>}
|
|
784
781
|
*/
|
|
785
|
-
async function ensureUaRuAbieNodeSelectorPatches(root, yamlFilesAbs, fail) {
|
|
782
|
+
async function ensureUaRuAbieNodeSelectorPatches(root, yamlFilesAbs, fail, passFn) {
|
|
786
783
|
const uaAbsList = yamlFilesAbs.filter(abs => isUaKustomizationPath(relative(root, abs).replaceAll('\\', '/') || abs))
|
|
787
784
|
if (uaAbsList.length === 0) {
|
|
788
785
|
fail(
|
|
@@ -806,7 +803,7 @@ async function ensureUaRuAbieNodeSelectorPatches(root, yamlFilesAbs, fail) {
|
|
|
806
803
|
)
|
|
807
804
|
return
|
|
808
805
|
}
|
|
809
|
-
|
|
806
|
+
passFn(`${rel}: nodeSelector patch (ua) відповідає abie.mdc`)
|
|
810
807
|
}
|
|
811
808
|
|
|
812
809
|
const ruAbsList = yamlFilesAbs.filter(abs => isRuKustomizationPath(relative(root, abs).replaceAll('\\', '/') || abs))
|
|
@@ -832,22 +829,23 @@ async function ensureUaRuAbieNodeSelectorPatches(root, yamlFilesAbs, fail) {
|
|
|
832
829
|
)
|
|
833
830
|
return
|
|
834
831
|
}
|
|
835
|
-
|
|
832
|
+
passFn(`${rel}: nodeSelector patch (ru) відповідає abie.mdc`)
|
|
836
833
|
}
|
|
837
834
|
}
|
|
838
835
|
|
|
839
836
|
/**
|
|
840
|
-
* Якщо є **Deployment** під **k8s**, вимагає в кожному overlay **ua** та **ru** patch **HTTPRoute
|
|
837
|
+
* Якщо є **Deployment** під **k8s**, вимагає в кожному overlay **ua** та **ru** patch **HTTPRoute** (непорожній **target.name**) за abie.mdc.
|
|
841
838
|
* @param {string} root корінь репозиторію
|
|
842
839
|
* @param {string[]} yamlFilesAbs yaml під k8s
|
|
843
840
|
* @param {(msg: string) => void} fail callback
|
|
841
|
+
* @param {(msg: string) => void} passFn успішне повідомлення
|
|
844
842
|
* @returns {Promise<void>}
|
|
845
843
|
*/
|
|
846
|
-
async function ensureUaRuAbieHttpRoutePatches(root, yamlFilesAbs, fail) {
|
|
844
|
+
async function ensureUaRuAbieHttpRoutePatches(root, yamlFilesAbs, fail, passFn) {
|
|
847
845
|
const uaAbsList = yamlFilesAbs.filter(abs => isUaKustomizationPath(relative(root, abs).replaceAll('\\', '/') || abs))
|
|
848
846
|
if (uaAbsList.length === 0) {
|
|
849
847
|
fail(
|
|
850
|
-
'Є Deployment у k8s — додай ua/kustomization.yaml з patch HTTPRoute
|
|
848
|
+
'Є Deployment у k8s — додай ua/kustomization.yaml з patch HTTPRoute (будь-який target.name: hostnames, parentRefs namespace ua) — abie.mdc'
|
|
851
849
|
)
|
|
852
850
|
return
|
|
853
851
|
}
|
|
@@ -867,13 +865,13 @@ async function ensureUaRuAbieHttpRoutePatches(root, yamlFilesAbs, fail) {
|
|
|
867
865
|
fail(`${rel}: ${v}`)
|
|
868
866
|
return
|
|
869
867
|
}
|
|
870
|
-
|
|
868
|
+
passFn(`${rel}: HTTPRoute patch (ua) відповідає abie.mdc`)
|
|
871
869
|
}
|
|
872
870
|
|
|
873
871
|
const ruAbsList = yamlFilesAbs.filter(abs => isRuKustomizationPath(relative(root, abs).replaceAll('\\', '/') || abs))
|
|
874
872
|
if (ruAbsList.length === 0) {
|
|
875
873
|
fail(
|
|
876
|
-
'Є Deployment у k8s — додай ru/kustomization.yaml з patch HTTPRoute
|
|
874
|
+
'Є Deployment у k8s — додай ru/kustomization.yaml з patch HTTPRoute (будь-який target.name: hostnames, namespace ru, gwin websocket) — abie.mdc'
|
|
877
875
|
)
|
|
878
876
|
return
|
|
879
877
|
}
|
|
@@ -893,7 +891,7 @@ async function ensureUaRuAbieHttpRoutePatches(root, yamlFilesAbs, fail) {
|
|
|
893
891
|
fail(`${rel}: ${v}`)
|
|
894
892
|
return
|
|
895
893
|
}
|
|
896
|
-
|
|
894
|
+
passFn(`${rel}: HTTPRoute patch (ru) відповідає abie.mdc`)
|
|
897
895
|
}
|
|
898
896
|
}
|
|
899
897
|
|
|
@@ -902,17 +900,14 @@ async function ensureUaRuAbieHttpRoutePatches(root, yamlFilesAbs, fail) {
|
|
|
902
900
|
* @returns {Promise<number>} 0 — OK, 1 — є порушення
|
|
903
901
|
*/
|
|
904
902
|
export async function check() {
|
|
905
|
-
|
|
906
|
-
const fail =
|
|
907
|
-
console.log(` ❌ ${msg}`)
|
|
908
|
-
exitCode = 1
|
|
909
|
-
}
|
|
903
|
+
const reporter = createCheckReporter()
|
|
904
|
+
const { pass, fail } = reporter
|
|
910
905
|
|
|
911
906
|
const root = process.cwd()
|
|
912
907
|
const enabled = await isAbieRuleEnabled(root)
|
|
913
908
|
if (!enabled) {
|
|
914
909
|
pass(`Правило abie не увімкнено в ${CONFIG_FILE} (rules) — перевірку пропущено`)
|
|
915
|
-
return
|
|
910
|
+
return reporter.getExitCode()
|
|
916
911
|
}
|
|
917
912
|
|
|
918
913
|
pass('Правило abie увімкнено — виконуємо перевірки')
|
|
@@ -978,7 +973,7 @@ export async function check() {
|
|
|
978
973
|
}
|
|
979
974
|
}
|
|
980
975
|
pass('Є Deployment — перевіряємо base: spec.template.spec.nodeSelector.preem (abie.mdc)')
|
|
981
|
-
await ensureAbieBaseDeploymentPreemNodeSelector(root, yamlFiles, fail)
|
|
976
|
+
await ensureAbieBaseDeploymentPreemNodeSelector(root, yamlFiles, fail, pass)
|
|
982
977
|
} else {
|
|
983
978
|
pass('Немає Deployment у дереві k8s — перевірку hc.yaml пропущено')
|
|
984
979
|
}
|
|
@@ -988,10 +983,10 @@ export async function check() {
|
|
|
988
983
|
|
|
989
984
|
if (deploymentDirs.size > 0) {
|
|
990
985
|
pass('Є Deployment — перевіряємо nodeSelector у ua/ru kustomization (abie.mdc)')
|
|
991
|
-
await ensureUaRuAbieNodeSelectorPatches(root, yamlFiles, fail)
|
|
992
|
-
pass('Є Deployment — перевіряємо HTTPRoute
|
|
993
|
-
await ensureUaRuAbieHttpRoutePatches(root, yamlFiles, fail)
|
|
986
|
+
await ensureUaRuAbieNodeSelectorPatches(root, yamlFiles, fail, pass)
|
|
987
|
+
pass('Є Deployment — перевіряємо HTTPRoute у ua/ru kustomization (abie.mdc)')
|
|
988
|
+
await ensureUaRuAbieHttpRoutePatches(root, yamlFiles, fail, pass)
|
|
994
989
|
}
|
|
995
990
|
|
|
996
|
-
return
|
|
991
|
+
return reporter.getExitCode()
|
|
997
992
|
}
|
package/scripts/check-bun.mjs
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
import { existsSync } from 'node:fs'
|
|
18
18
|
import { readFile } from 'node:fs/promises'
|
|
19
19
|
|
|
20
|
-
import {
|
|
20
|
+
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Чи ім'я пакета дозволене в кореневих `devDependencies` за bun.mdc (лише `@cspell/*` та `@nitra/*`).
|
|
@@ -53,11 +53,8 @@ async function loadNCursorRules() {
|
|
|
53
53
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
54
54
|
*/
|
|
55
55
|
export async function check() {
|
|
56
|
-
|
|
57
|
-
const fail =
|
|
58
|
-
console.log(` ❌ ${msg}`)
|
|
59
|
-
exitCode = 1
|
|
60
|
-
}
|
|
56
|
+
const reporter = createCheckReporter()
|
|
57
|
+
const { pass, fail } = reporter
|
|
61
58
|
|
|
62
59
|
const forbidden = ['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', '.yarnrc.yml']
|
|
63
60
|
for (const f of forbidden) {
|
|
@@ -164,5 +161,5 @@ export async function check() {
|
|
|
164
161
|
}
|
|
165
162
|
}
|
|
166
163
|
|
|
167
|
-
return
|
|
164
|
+
return reporter.getExitCode()
|
|
168
165
|
}
|
package/scripts/check-docker.mjs
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { basename } from 'node:path'
|
|
9
9
|
|
|
10
10
|
import { lintDockerfileWithHadolint, posixRel } from './utils/docker-hadolint.mjs'
|
|
11
|
-
import {
|
|
11
|
+
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
12
12
|
import { walkDir } from './utils/walkDir.mjs'
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -42,18 +42,15 @@ export async function findDockerfilePaths(root) {
|
|
|
42
42
|
* @returns {Promise<number>} 0 — все OK, 1 — є зауваження або помилка запуску
|
|
43
43
|
*/
|
|
44
44
|
export async function check() {
|
|
45
|
-
|
|
46
|
-
const fail =
|
|
47
|
-
console.log(` ❌ ${msg}`)
|
|
48
|
-
exitCode = 1
|
|
49
|
-
}
|
|
45
|
+
const reporter = createCheckReporter()
|
|
46
|
+
const { pass, fail } = reporter
|
|
50
47
|
|
|
51
48
|
const root = process.cwd()
|
|
52
49
|
const files = await findDockerfilePaths(root)
|
|
53
50
|
|
|
54
51
|
if (files.length === 0) {
|
|
55
52
|
pass('Немає Dockerfile / Containerfile — перевірку hadolint пропущено')
|
|
56
|
-
return
|
|
53
|
+
return reporter.getExitCode()
|
|
57
54
|
}
|
|
58
55
|
|
|
59
56
|
pass(`Знайдено файлів для hadolint: ${files.length}`)
|
|
@@ -69,5 +66,5 @@ export async function check() {
|
|
|
69
66
|
}
|
|
70
67
|
}
|
|
71
68
|
|
|
72
|
-
return
|
|
69
|
+
return reporter.getExitCode()
|
|
73
70
|
}
|
package/scripts/check-ga.mjs
CHANGED
|
@@ -14,7 +14,7 @@ import { existsSync } from 'node:fs'
|
|
|
14
14
|
import { readdir, readFile } from 'node:fs/promises'
|
|
15
15
|
import { join } from 'node:path'
|
|
16
16
|
|
|
17
|
-
import {
|
|
17
|
+
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
18
18
|
import {
|
|
19
19
|
anyRunStepIncludes,
|
|
20
20
|
eventPathsIncludeExact,
|
|
@@ -122,17 +122,14 @@ function verifyNoDirectBunOrCache(relPath, content, failFn, passFn) {
|
|
|
122
122
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
123
123
|
*/
|
|
124
124
|
export async function check() {
|
|
125
|
-
|
|
126
|
-
const fail =
|
|
127
|
-
console.log(` ❌ ${msg}`)
|
|
128
|
-
exitCode = 1
|
|
129
|
-
}
|
|
125
|
+
const reporter = createCheckReporter()
|
|
126
|
+
const { pass, fail } = reporter
|
|
130
127
|
|
|
131
128
|
const wfDir = '.github/workflows'
|
|
132
129
|
|
|
133
130
|
if (!existsSync(wfDir)) {
|
|
134
131
|
fail(`Директорія ${wfDir} не існує`)
|
|
135
|
-
return
|
|
132
|
+
return reporter.getExitCode()
|
|
136
133
|
}
|
|
137
134
|
|
|
138
135
|
const setupBunDepsAction = '.github/actions/setup-bun-deps/action.yml'
|
|
@@ -290,5 +287,5 @@ export async function check() {
|
|
|
290
287
|
}
|
|
291
288
|
}
|
|
292
289
|
|
|
293
|
-
return
|
|
290
|
+
return reporter.getExitCode()
|
|
294
291
|
}
|
|
@@ -6,18 +6,15 @@
|
|
|
6
6
|
import { existsSync } from 'node:fs'
|
|
7
7
|
import { readFile } from 'node:fs/promises'
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Перевіряє відповідність проєкту правилам js-format.mdc
|
|
13
13
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
14
14
|
*/
|
|
15
15
|
export async function check() {
|
|
16
|
-
|
|
17
|
-
const fail =
|
|
18
|
-
console.log(` ❌ ${msg}`)
|
|
19
|
-
exitCode = 1
|
|
20
|
-
}
|
|
16
|
+
const reporter = createCheckReporter()
|
|
17
|
+
const { pass, fail } = reporter
|
|
21
18
|
|
|
22
19
|
const expectedKeys = [
|
|
23
20
|
'arrowParens',
|
|
@@ -95,5 +92,5 @@ export async function check() {
|
|
|
95
92
|
if (pkg.prettier) fail('package.json містить поле "prettier" — видали його')
|
|
96
93
|
}
|
|
97
94
|
|
|
98
|
-
return
|
|
95
|
+
return reporter.getExitCode()
|
|
99
96
|
}
|
|
@@ -10,7 +10,7 @@ import { existsSync } from 'node:fs'
|
|
|
10
10
|
import { readFile } from 'node:fs/promises'
|
|
11
11
|
|
|
12
12
|
import { parseWorkflowYaml, verifyLintJsWorkflowStructure } from './utils/gha-workflow.mjs'
|
|
13
|
-
import {
|
|
13
|
+
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
14
14
|
|
|
15
15
|
/** Очікуваний локальний скрипт. */
|
|
16
16
|
export const CANONICAL_LINT_JS = 'bunx oxlint --fix && bunx eslint --fix . && bunx jscpd .'
|
|
@@ -41,11 +41,8 @@ export function isCanonicalLintJs(script) {
|
|
|
41
41
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
42
42
|
*/
|
|
43
43
|
export async function check() {
|
|
44
|
-
|
|
45
|
-
const fail =
|
|
46
|
-
console.log(` ❌ ${msg}`)
|
|
47
|
-
exitCode = 1
|
|
48
|
-
}
|
|
44
|
+
const reporter = createCheckReporter()
|
|
45
|
+
const { pass, fail } = reporter
|
|
49
46
|
|
|
50
47
|
let eslintPath = ''
|
|
51
48
|
if (existsSync('eslint.config.js')) {
|
|
@@ -242,5 +239,5 @@ export async function check() {
|
|
|
242
239
|
if (existsSync(dup)) fail(`Знайдено застарілий конфіг ESLint: ${dup} — видали, використовуй flat config`)
|
|
243
240
|
}
|
|
244
241
|
|
|
245
|
-
return
|
|
242
|
+
return reporter.getExitCode()
|
|
246
243
|
}
|
|
@@ -8,16 +8,17 @@ import { existsSync } from 'node:fs'
|
|
|
8
8
|
import { readFile } from 'node:fs/promises'
|
|
9
9
|
import { join } from 'node:path'
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
12
12
|
import { getMonorepoPackageRootDirs } from './utils/workspaces.mjs'
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Перевіряє відповідність правилам js-pino.mdc для одного workspace-пакета.
|
|
16
16
|
* @param {string} rootDir відносний шлях workspace (не `'.'`)
|
|
17
17
|
* @param {(msg: string) => void} fail функція зворотного виклику для реєстрації помилки перевірки
|
|
18
|
+
* @param {(msg: string) => void} passFn успішне повідомлення (як у check-reporter)
|
|
18
19
|
* @returns {Promise<void>} завершується після перевірок цього пакета
|
|
19
20
|
*/
|
|
20
|
-
async function checkWorkspacePackage(rootDir, fail) {
|
|
21
|
+
async function checkWorkspacePackage(rootDir, fail, passFn) {
|
|
21
22
|
const label = `[${rootDir}] `
|
|
22
23
|
const pkgPath = join(rootDir, 'package.json')
|
|
23
24
|
if (existsSync(pkgPath)) {
|
|
@@ -36,9 +37,9 @@ async function checkWorkspacePackage(rootDir, fail) {
|
|
|
36
37
|
if (existsSync(configmapPath)) {
|
|
37
38
|
const content = await readFile(configmapPath, 'utf8')
|
|
38
39
|
if (content.includes('OTEL_RESOURCE_ATTRIBUTES')) {
|
|
39
|
-
|
|
40
|
+
passFn(`${label}k8s/base/configmap.yaml містить OTEL_RESOURCE_ATTRIBUTES`)
|
|
40
41
|
if (content.includes('service.name=') && content.includes('service.namespace=')) {
|
|
41
|
-
|
|
42
|
+
passFn(`${label}OTEL_RESOURCE_ATTRIBUTES містить service.name та service.namespace`)
|
|
42
43
|
} else {
|
|
43
44
|
fail(`${label}OTEL_RESOURCE_ATTRIBUTES має містити service.name=<name>,service.namespace=<namespace>`)
|
|
44
45
|
}
|
|
@@ -53,23 +54,20 @@ async function checkWorkspacePackage(rootDir, fail) {
|
|
|
53
54
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
54
55
|
*/
|
|
55
56
|
export async function check() {
|
|
56
|
-
|
|
57
|
-
const fail =
|
|
58
|
-
console.log(` ❌ ${msg}`)
|
|
59
|
-
exitCode = 1
|
|
60
|
-
}
|
|
57
|
+
const reporter = createCheckReporter()
|
|
58
|
+
const { pass, fail } = reporter
|
|
61
59
|
|
|
62
60
|
const roots = await getMonorepoPackageRootDirs()
|
|
63
61
|
const workspaceRoots = roots.filter(r => r !== '.')
|
|
64
62
|
|
|
65
63
|
if (workspaceRoots.length === 0) {
|
|
66
64
|
pass('js-pino: немає workspace-пакетів у кореневому package.json — перевірку залежностей і k8s у пакетах пропущено')
|
|
67
|
-
return
|
|
65
|
+
return reporter.getExitCode()
|
|
68
66
|
}
|
|
69
67
|
|
|
70
68
|
for (const r of workspaceRoots) {
|
|
71
|
-
await checkWorkspacePackage(r, fail)
|
|
69
|
+
await checkWorkspacePackage(r, fail, pass)
|
|
72
70
|
}
|
|
73
71
|
|
|
74
|
-
return
|
|
72
|
+
return reporter.getExitCode()
|
|
75
73
|
}
|
package/scripts/check-k8s.mjs
CHANGED
|
@@ -51,7 +51,7 @@ import { basename, dirname, join, relative, resolve } from 'node:path'
|
|
|
51
51
|
|
|
52
52
|
import { parseAllDocuments } from 'yaml'
|
|
53
53
|
|
|
54
|
-
import {
|
|
54
|
+
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
55
55
|
import { walkDir } from './utils/walkDir.mjs'
|
|
56
56
|
|
|
57
57
|
/** Версія набору схем yannh — узгоджено з k8s.mdc */
|
|
@@ -1482,11 +1482,8 @@ async function ensureBaseKustomizationHasNamespace(root, yamlFiles, fail) {
|
|
|
1482
1482
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
1483
1483
|
*/
|
|
1484
1484
|
export async function check() {
|
|
1485
|
-
|
|
1486
|
-
const fail =
|
|
1487
|
-
console.log(` ❌ ${msg}`)
|
|
1488
|
-
exitCode = 1
|
|
1489
|
-
}
|
|
1485
|
+
const reporter = createCheckReporter()
|
|
1486
|
+
const { pass, fail } = reporter
|
|
1490
1487
|
|
|
1491
1488
|
const root = process.cwd()
|
|
1492
1489
|
|
|
@@ -1496,7 +1493,7 @@ export async function check() {
|
|
|
1496
1493
|
|
|
1497
1494
|
if (yamlFiles.length === 0) {
|
|
1498
1495
|
pass('Немає *.yaml під k8s — перевірку $schema пропущено')
|
|
1499
|
-
return
|
|
1496
|
+
return reporter.getExitCode()
|
|
1500
1497
|
}
|
|
1501
1498
|
|
|
1502
1499
|
pass(`YAML у k8s: ${yamlFiles.length} файл(ів)`)
|
|
@@ -1515,5 +1512,5 @@ export async function check() {
|
|
|
1515
1512
|
|
|
1516
1513
|
await ensureBaseKustomizationHasNamespace(root, yamlFiles, fail)
|
|
1517
1514
|
|
|
1518
|
-
return
|
|
1515
|
+
return reporter.getExitCode()
|
|
1519
1516
|
}
|
|
@@ -18,7 +18,7 @@ import { readdir, readFile, rename, unlink, writeFile } from 'node:fs/promises'
|
|
|
18
18
|
import { basename, dirname, join, relative } from 'node:path'
|
|
19
19
|
|
|
20
20
|
import { findDockerfilePaths } from './check-docker.mjs'
|
|
21
|
-
import {
|
|
21
|
+
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
22
22
|
import { walkDir } from './utils/walkDir.mjs'
|
|
23
23
|
|
|
24
24
|
/**
|
|
@@ -264,11 +264,8 @@ function dockerfileHasEnvsSubstTemplate(dockerfileContent) {
|
|
|
264
264
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
265
265
|
*/
|
|
266
266
|
export async function check() {
|
|
267
|
-
|
|
268
|
-
const fail =
|
|
269
|
-
console.log(` ❌ ${msg}`)
|
|
270
|
-
exitCode = 1
|
|
271
|
-
}
|
|
267
|
+
const reporter = createCheckReporter()
|
|
268
|
+
const { pass, fail } = reporter
|
|
272
269
|
|
|
273
270
|
const root = process.cwd()
|
|
274
271
|
|
|
@@ -284,7 +281,7 @@ export async function check() {
|
|
|
284
281
|
|
|
285
282
|
if (templates.length === 0) {
|
|
286
283
|
pass('Немає default.conf.template — перевірку nginx-default-tpl пропущено')
|
|
287
|
-
return
|
|
284
|
+
return reporter.getExitCode()
|
|
288
285
|
}
|
|
289
286
|
|
|
290
287
|
pass(`Знайдено default.conf.template: ${templates.length}`)
|
|
@@ -383,5 +380,5 @@ export async function check() {
|
|
|
383
380
|
fail('Очікується .vscode/settings.json з форматером nginx і formatOnSave (див. nginx-default-tpl.mdc)')
|
|
384
381
|
}
|
|
385
382
|
|
|
386
|
-
return
|
|
383
|
+
return reporter.getExitCode()
|
|
387
384
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { existsSync } from 'node:fs'
|
|
8
8
|
import { readFile, stat } from 'node:fs/promises'
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
11
11
|
import {
|
|
12
12
|
hasIdTokenWritePermission,
|
|
13
13
|
hasNpmPublishStepWithPackage,
|
|
@@ -21,11 +21,8 @@ import {
|
|
|
21
21
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
22
22
|
*/
|
|
23
23
|
export async function check() {
|
|
24
|
-
|
|
25
|
-
const fail =
|
|
26
|
-
console.log(` ❌ ${msg}`)
|
|
27
|
-
exitCode = 1
|
|
28
|
-
}
|
|
24
|
+
const reporter = createCheckReporter()
|
|
25
|
+
const { pass, fail } = reporter
|
|
29
26
|
|
|
30
27
|
if (existsSync('package.json')) {
|
|
31
28
|
pass('package.json існує')
|
|
@@ -114,5 +111,5 @@ export async function check() {
|
|
|
114
111
|
fail(`Відсутній ${publishWf} (npm-module.mdc: npm publish)`)
|
|
115
112
|
}
|
|
116
113
|
|
|
117
|
-
return
|
|
114
|
+
return reporter.getExitCode()
|
|
118
115
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { existsSync } from 'node:fs'
|
|
9
9
|
import { readFile } from 'node:fs/promises'
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
12
12
|
import { anyRunStepIncludesStylelint, parseWorkflowYaml } from './utils/gha-workflow.mjs'
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -16,11 +16,8 @@ import { anyRunStepIncludesStylelint, parseWorkflowYaml } from './utils/gha-work
|
|
|
16
16
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
17
17
|
*/
|
|
18
18
|
export async function check() {
|
|
19
|
-
|
|
20
|
-
const fail =
|
|
21
|
-
console.log(` ❌ ${msg}`)
|
|
22
|
-
exitCode = 1
|
|
23
|
-
}
|
|
19
|
+
const reporter = createCheckReporter()
|
|
20
|
+
const { pass, fail } = reporter
|
|
24
21
|
|
|
25
22
|
if (existsSync('package.json')) {
|
|
26
23
|
const pkg = JSON.parse(await readFile('package.json', 'utf8'))
|
|
@@ -105,5 +102,5 @@ export async function check() {
|
|
|
105
102
|
}
|
|
106
103
|
}
|
|
107
104
|
|
|
108
|
-
return
|
|
105
|
+
return reporter.getExitCode()
|
|
109
106
|
}
|
package/scripts/check-text.mjs
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { existsSync } from 'node:fs'
|
|
13
13
|
import { readFile } from 'node:fs/promises'
|
|
14
14
|
|
|
15
|
-
import {
|
|
15
|
+
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
16
16
|
import { anyRunStepIncludes, parseWorkflowYaml } from './utils/gha-workflow.mjs'
|
|
17
17
|
|
|
18
18
|
/** Заголовок абзацу про апостроф у text.mdc / n-text.mdc. */
|
|
@@ -47,11 +47,8 @@ function verifyUkApostropheRuleParagraph(filePath, body, failFn, passFn) {
|
|
|
47
47
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
48
48
|
*/
|
|
49
49
|
export async function check() {
|
|
50
|
-
|
|
51
|
-
const fail =
|
|
52
|
-
console.log(` ❌ ${msg}`)
|
|
53
|
-
exitCode = 1
|
|
54
|
-
}
|
|
50
|
+
const reporter = createCheckReporter()
|
|
51
|
+
const { pass, fail } = reporter
|
|
55
52
|
|
|
56
53
|
const v8rIgnoreRequired = ['.vscode/extensions.json', '.vscode/settings.json']
|
|
57
54
|
if (existsSync('.v8rignore')) {
|
|
@@ -215,5 +212,5 @@ export async function check() {
|
|
|
215
212
|
}
|
|
216
213
|
}
|
|
217
214
|
|
|
218
|
-
return
|
|
215
|
+
return reporter.getExitCode()
|
|
219
216
|
}
|
package/scripts/check-vue.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { existsSync } from 'node:fs'
|
|
|
11
11
|
import { readFile } from 'node:fs/promises'
|
|
12
12
|
import { join, relative } from 'node:path'
|
|
13
13
|
|
|
14
|
-
import {
|
|
14
|
+
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
15
15
|
import {
|
|
16
16
|
findForbiddenVueImportsInSourceFile,
|
|
17
17
|
isVueImportScanSourceFile,
|
|
@@ -53,9 +53,10 @@ function ukFilesCountPhrase(n) {
|
|
|
53
53
|
* Перевіряє залежності та vite.config одного Vue-пакета.
|
|
54
54
|
* @param {string} rootDir відносний шлях до пакета
|
|
55
55
|
* @param {(msg: string) => void} fail функція зворотного виклику для реєстрації помилки перевірки
|
|
56
|
+
* @param {(msg: string) => void} passFn успішне повідомлення (як у check-reporter)
|
|
56
57
|
* @returns {Promise<void>} завершується після перевірок залежностей, `vite.config` і сканування джерел на імпорти з `vue`
|
|
57
58
|
*/
|
|
58
|
-
async function checkVuePackage(rootDir, fail) {
|
|
59
|
+
async function checkVuePackage(rootDir, fail, passFn) {
|
|
59
60
|
const label = packageLabel(rootDir)
|
|
60
61
|
const prefix = `[${label}] `
|
|
61
62
|
|
|
@@ -66,7 +67,7 @@ async function checkVuePackage(rootDir, fail) {
|
|
|
66
67
|
const allDeps = { ...deps, ...devDeps }
|
|
67
68
|
|
|
68
69
|
if (deps.vue) {
|
|
69
|
-
|
|
70
|
+
passFn(`${prefix}vue в dependencies: ${deps.vue}`)
|
|
70
71
|
} else {
|
|
71
72
|
fail(`${prefix}vue відсутній в dependencies`)
|
|
72
73
|
}
|
|
@@ -74,7 +75,7 @@ async function checkVuePackage(rootDir, fail) {
|
|
|
74
75
|
if (devDeps.vite) {
|
|
75
76
|
const match = devDeps.vite.match(/(\d+)/)
|
|
76
77
|
if (match && Number(match[1]) >= 8) {
|
|
77
|
-
|
|
78
|
+
passFn(`${prefix}vite >= 8: ${devDeps.vite}`)
|
|
78
79
|
} else {
|
|
79
80
|
fail(`${prefix}vite має бути >= 8, знайдено: ${devDeps.vite}`)
|
|
80
81
|
}
|
|
@@ -83,25 +84,25 @@ async function checkVuePackage(rootDir, fail) {
|
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
if (devDeps['@vitejs/plugin-vue']) {
|
|
86
|
-
|
|
87
|
+
passFn(`${prefix}@vitejs/plugin-vue: ${devDeps['@vitejs/plugin-vue']}`)
|
|
87
88
|
} else {
|
|
88
89
|
fail(`${prefix}@vitejs/plugin-vue відсутній в devDependencies`)
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
if (allDeps['vue-macros']) {
|
|
92
|
-
|
|
93
|
+
passFn(`${prefix}vue-macros: ${allDeps['vue-macros']}`)
|
|
93
94
|
} else {
|
|
94
95
|
fail(`${prefix}vue-macros відсутній — bun add -d vue-macros`)
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
if (allDeps['unplugin-auto-import']) {
|
|
98
|
-
|
|
99
|
+
passFn(`${prefix}unplugin-auto-import присутній`)
|
|
99
100
|
} else {
|
|
100
101
|
fail(`${prefix}unplugin-auto-import відсутній — bun add -d unplugin-auto-import`)
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
if (allDeps['vite-plugin-vue-layouts-next']) {
|
|
104
|
-
|
|
105
|
+
passFn(`${prefix}vite-plugin-vue-layouts-next присутній`)
|
|
105
106
|
} else {
|
|
106
107
|
fail(`${prefix}vite-plugin-vue-layouts-next відсутній — bun add -d vite-plugin-vue-layouts-next`)
|
|
107
108
|
}
|
|
@@ -112,12 +113,12 @@ async function checkVuePackage(rootDir, fail) {
|
|
|
112
113
|
const relConfig = join(rootDir, viteConfig)
|
|
113
114
|
const content = await readFile(relConfig, 'utf8')
|
|
114
115
|
if (content.includes('VueMacros')) {
|
|
115
|
-
|
|
116
|
+
passFn(`${prefix}${viteConfig} використовує VueMacros`)
|
|
116
117
|
} else {
|
|
117
118
|
fail(`${prefix}${viteConfig} не містить VueMacros`)
|
|
118
119
|
}
|
|
119
120
|
if (content.includes('AutoImport')) {
|
|
120
|
-
|
|
121
|
+
passFn(`${prefix}${viteConfig} використовує AutoImport`)
|
|
121
122
|
} else {
|
|
122
123
|
fail(`${prefix}${viteConfig} не містить AutoImport`)
|
|
123
124
|
}
|
|
@@ -147,7 +148,7 @@ async function checkVuePackage(rootDir, fail) {
|
|
|
147
148
|
}
|
|
148
149
|
}
|
|
149
150
|
if (importViolations === 0) {
|
|
150
|
-
|
|
151
|
+
passFn(
|
|
151
152
|
`${prefix}немає заборонених value-імпортів з 'vue' у джерелах (проскановано ${ukFilesCountPhrase(sourcePaths.length)})`
|
|
152
153
|
)
|
|
153
154
|
}
|
|
@@ -158,11 +159,8 @@ async function checkVuePackage(rootDir, fail) {
|
|
|
158
159
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
159
160
|
*/
|
|
160
161
|
export async function check() {
|
|
161
|
-
|
|
162
|
-
const fail =
|
|
163
|
-
console.log(` ❌ ${msg}`)
|
|
164
|
-
exitCode = 1
|
|
165
|
-
}
|
|
162
|
+
const reporter = createCheckReporter()
|
|
163
|
+
const { pass, fail } = reporter
|
|
166
164
|
|
|
167
165
|
if (existsSync('.vscode/extensions.json')) {
|
|
168
166
|
const ext = JSON.parse(await readFile('.vscode/extensions.json', 'utf8'))
|
|
@@ -188,12 +186,12 @@ export async function check() {
|
|
|
188
186
|
|
|
189
187
|
if (vueRoots.length === 0) {
|
|
190
188
|
fail('vue не знайдено в dependencies жодного пакета (корінь репо та каталоги з кореневого workspaces)')
|
|
191
|
-
return
|
|
189
|
+
return reporter.getExitCode()
|
|
192
190
|
}
|
|
193
191
|
|
|
194
192
|
for (const r of vueRoots) {
|
|
195
|
-
await checkVuePackage(r, fail)
|
|
193
|
+
await checkVuePackage(r, fail, pass)
|
|
196
194
|
}
|
|
197
195
|
|
|
198
|
-
return
|
|
196
|
+
return reporter.getExitCode()
|
|
199
197
|
}
|
package/scripts/run-docker.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { basename } from 'node:path'
|
|
|
11
11
|
|
|
12
12
|
import { isRunAsCli } from './cli-entry.mjs'
|
|
13
13
|
import { lintDockerfileWithHadolint, posixRel } from './utils/docker-hadolint.mjs'
|
|
14
|
-
import {
|
|
14
|
+
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
15
15
|
import { walkDir } from './utils/walkDir.mjs'
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -44,18 +44,15 @@ export async function findLintDockerfilePaths(root) {
|
|
|
44
44
|
* @returns {Promise<number>} 0 — OK, 1 — зауваження або помилка
|
|
45
45
|
*/
|
|
46
46
|
async function main() {
|
|
47
|
-
|
|
48
|
-
const fail =
|
|
49
|
-
console.log(` ❌ ${msg}`)
|
|
50
|
-
exitCode = 1
|
|
51
|
-
}
|
|
47
|
+
const reporter = createCheckReporter()
|
|
48
|
+
const { pass, fail } = reporter
|
|
52
49
|
|
|
53
50
|
const root = process.cwd()
|
|
54
51
|
const files = await findLintDockerfilePaths(root)
|
|
55
52
|
|
|
56
53
|
if (files.length === 0) {
|
|
57
54
|
pass('lint-docker: немає Dockerfile / *.Dockerfile — hadolint пропущено')
|
|
58
|
-
return
|
|
55
|
+
return reporter.getExitCode()
|
|
59
56
|
}
|
|
60
57
|
|
|
61
58
|
pass(`lint-docker: файлів для hadolint: ${files.length}`)
|
|
@@ -71,7 +68,7 @@ async function main() {
|
|
|
71
68
|
}
|
|
72
69
|
}
|
|
73
70
|
|
|
74
|
-
return
|
|
71
|
+
return reporter.getExitCode()
|
|
75
72
|
}
|
|
76
73
|
|
|
77
74
|
if (isRunAsCli()) {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Спільний репортер для check-скриптів і lint-docker.
|
|
3
|
+
*
|
|
4
|
+
* Об’єднує вивід успіхів (`pass` з `pass.mjs`) і помилок з префіксом ❌; накопичує код виходу **1**,
|
|
5
|
+
* якщо хоча б раз викликано `fail`.
|
|
6
|
+
*
|
|
7
|
+
* Використовуй `getExitCode()` у `return`, а не деструктуризацію `exitCode` — геттер «знімається» один раз.
|
|
8
|
+
*/
|
|
9
|
+
import { pass } from './pass.mjs'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Створює пару `pass` / `fail` з накопиченням ненульового коду виходу.
|
|
13
|
+
* @returns {{ pass: typeof pass, fail: (msg: string) => void, getExitCode: () => number }} об’єкт з `pass`, `fail` і `getExitCode()` (0 або 1 після будь-якого `fail`)
|
|
14
|
+
*/
|
|
15
|
+
export function createCheckReporter() {
|
|
16
|
+
let exitCode = 0
|
|
17
|
+
return {
|
|
18
|
+
pass,
|
|
19
|
+
fail(msg) {
|
|
20
|
+
console.log(` ❌ ${msg}`)
|
|
21
|
+
exitCode = 1
|
|
22
|
+
},
|
|
23
|
+
getExitCode() {
|
|
24
|
+
return exitCode
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|