@nitra/cursor 1.5.2 → 1.5.5
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/ga.mdc +87 -2
- package/mdc/js-lint.mdc +17 -2
- package/mdc/js-pino.mdc +1 -1
- package/mdc/npm-module.mdc +46 -0
- package/mdc/text.mdc +13 -3
- package/package.json +2 -1
- package/schemas/n-cursor.json +27 -0
- package/scripts/check-ga.mjs +1 -1
- package/scripts/check-js-lint.mjs +1 -3
- package/scripts/check-js-pino.mjs +51 -18
- package/scripts/check-vue.mjs +105 -61
- package/scripts/utils/workspaces.mjs +52 -0
package/mdc/ga.mdc
CHANGED
|
@@ -41,6 +41,9 @@ on:
|
|
|
41
41
|
jobs:
|
|
42
42
|
cleanup_old_workflows:
|
|
43
43
|
runs-on: ubuntu-latest
|
|
44
|
+
permissions:
|
|
45
|
+
actions: write
|
|
46
|
+
contents: read
|
|
44
47
|
steps:
|
|
45
48
|
- name: Delete workflow runs
|
|
46
49
|
uses: dmvict/clean-workflow-runs@v1.0.0
|
|
@@ -67,6 +70,8 @@ on:
|
|
|
67
70
|
jobs:
|
|
68
71
|
cleanup_old_branches:
|
|
69
72
|
runs-on: ubuntu-latest
|
|
73
|
+
permissions:
|
|
74
|
+
contents: write
|
|
70
75
|
steps:
|
|
71
76
|
- id: delete_stuff
|
|
72
77
|
name: Delete those pesky dead branches
|
|
@@ -78,11 +83,68 @@ jobs:
|
|
|
78
83
|
dry_run: no
|
|
79
84
|
|
|
80
85
|
- name: Get output
|
|
81
|
-
|
|
86
|
+
env:
|
|
87
|
+
DELETED_BRANCHES: ${{ steps.delete_stuff.outputs.deleted_branches }}
|
|
88
|
+
run: |
|
|
89
|
+
echo "Deleted branches: ${DELETED_BRANCHES}"
|
|
82
90
|
```
|
|
83
91
|
|
|
84
92
|
якщо в ignore_branches задані інші бранчі, то це допустимо.
|
|
85
93
|
|
|
94
|
+
Повинен бути файл .github/workflows/lint-ga.yml, зі змістом:
|
|
95
|
+
|
|
96
|
+
```yaml
|
|
97
|
+
name: Lint GA
|
|
98
|
+
|
|
99
|
+
on:
|
|
100
|
+
push:
|
|
101
|
+
branches:
|
|
102
|
+
- dev
|
|
103
|
+
paths:
|
|
104
|
+
- '.github/workflows/**'
|
|
105
|
+
- '.github/zizmor.yml'
|
|
106
|
+
- 'package.json'
|
|
107
|
+
- 'bun.lock'
|
|
108
|
+
|
|
109
|
+
pull_request:
|
|
110
|
+
branches:
|
|
111
|
+
- dev
|
|
112
|
+
|
|
113
|
+
concurrency:
|
|
114
|
+
group: ${{ github.ref }}-${{ github.workflow }}
|
|
115
|
+
cancel-in-progress: true
|
|
116
|
+
|
|
117
|
+
jobs:
|
|
118
|
+
lint-ga:
|
|
119
|
+
runs-on: ubuntu-latest
|
|
120
|
+
permissions:
|
|
121
|
+
contents: read
|
|
122
|
+
steps:
|
|
123
|
+
- uses: actions/checkout@v4
|
|
124
|
+
with:
|
|
125
|
+
persist-credentials: false
|
|
126
|
+
|
|
127
|
+
- uses: oven-sh/setup-bun@v2
|
|
128
|
+
|
|
129
|
+
- name: Cache Bun dependencies
|
|
130
|
+
uses: actions/cache@v4
|
|
131
|
+
with:
|
|
132
|
+
path: |
|
|
133
|
+
~/.bun/install/cache
|
|
134
|
+
node_modules
|
|
135
|
+
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
|
|
136
|
+
restore-keys: |
|
|
137
|
+
${{ runner.os }}-bun-
|
|
138
|
+
|
|
139
|
+
- name: Install dependencies
|
|
140
|
+
run: bun install --frozen-lockfile
|
|
141
|
+
|
|
142
|
+
- uses: astral-sh/setup-uv@v6
|
|
143
|
+
|
|
144
|
+
- name: Lint GA
|
|
145
|
+
run: bun run lint-ga
|
|
146
|
+
```
|
|
147
|
+
|
|
86
148
|
в файлі .vscode/extensions.json є налаштування для GitHub Actions:
|
|
87
149
|
|
|
88
150
|
```json title=".vscode/extensions.json"
|
|
@@ -91,6 +153,29 @@ jobs:
|
|
|
91
153
|
}
|
|
92
154
|
```
|
|
93
155
|
|
|
156
|
+
## actionlint
|
|
157
|
+
|
|
158
|
+
Статична перевірка синтаксису та виразів GitHub Actions: [actionlint](https://github.com/rhysd/actionlint).
|
|
159
|
+
|
|
160
|
+
У кореневому `package.json` у скрипті `lint-ga` викликай **actionlint** через **`bunx node-actionlint`** (пакет [node-actionlint](https://www.npmjs.com/package/node-actionlint) постає бінарник actionlint для Node-екосистеми).
|
|
161
|
+
|
|
162
|
+
## zizmor
|
|
163
|
+
|
|
164
|
+
Статичний аналіз безпеки для GitHub Actions: [zizmor documentation](https://docs.zizmor.sh).
|
|
165
|
+
|
|
166
|
+
У кореневому `package.json` має бути скрипт:
|
|
167
|
+
|
|
168
|
+
```json title="package.json"
|
|
169
|
+
"scripts": {
|
|
170
|
+
"lint-ga": "bunx node-actionlint && uvx zizmor --offline --collect=workflows ."
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Параметр `--offline` обмежує аналіз офлайн-аудитами (без GitHub API);
|
|
175
|
+
|
|
176
|
+
За замовчуванням audit [unpinned-uses](https://docs.zizmor.sh/audits/#unpinned-uses) вимагає повний commit SHA для кожного `uses:`. У всих проєктах прийняті **семантичні теги** (`@v4`, `@v2` тощо), додай `.github/zizmor.yml` з політикою `ref-pin` (приклад у цьому репозиторії).
|
|
177
|
+
|
|
94
178
|
## Перевірка
|
|
95
179
|
|
|
96
|
-
`
|
|
180
|
+
- `bun run lint-ga` — actionlint (node-actionlint) і zizmor
|
|
181
|
+
- `npx @nitra/cursor check ga`
|
package/mdc/js-lint.mdc
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Перевірка JavaScript коду
|
|
3
3
|
alwaysApply: true
|
|
4
|
-
version: '1.
|
|
4
|
+
version: '1.5'
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
Перевірка виконується за допомогою **oxlint**, **ESLint** та **jscpd** (дублікати коду).
|
|
@@ -25,7 +25,7 @@ version: '1.4'
|
|
|
25
25
|
"lint-js": "oxlint --fix && bunx eslint --fix . && bunx jscpd ."
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@nitra/eslint-config": "^3.
|
|
28
|
+
"@nitra/eslint-config": "^3.4.0"
|
|
29
29
|
}
|
|
30
30
|
```
|
|
31
31
|
|
|
@@ -43,6 +43,17 @@ version: '1.4'
|
|
|
43
43
|
}
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
+
## jscpd: рефакторинг і структура
|
|
47
|
+
|
|
48
|
+
Коли **jscpd** знаходить клони, спочатку зменшуй дублювання кодом, а не конфігом.
|
|
49
|
+
|
|
50
|
+
- **Рефакторинг:** винеси спільні фрагменти в функції, модулі, утиліти, composables/hooks, спільні компоненти або базові типи — залежно від контексту.
|
|
51
|
+
- **Структура:** якщо одна й та сама логіка розмазана між файлами чи пакетами, запропонуй зміну структури (наприклад, спільний модуль, `shared/`, внутрішній пакет у monorepo), щоб була **одна канонічна реалізація** і повторні місця лише викликали її.
|
|
52
|
+
|
|
53
|
+
Так ти уникаєш **хибних обходів** перевірки: розширення `ignore` чи завищення `minLines` лише щоб прибрати звіт — не заміна рефакторингу для справжніх клонів. Якщо збіг **семантично випадковий** (генерований код, формальні шаблони без спільної логіки), після оцінки допустимо точковий `ignore` або зміна порогу — з коротким обґрунтуванням.
|
|
54
|
+
|
|
55
|
+
Але обов'язково перед рефакторингом перевір чи є тести на блоки які підлягають зміні, а саме Bun.test для js та playwright для vue. Якщо є, то перевір чи вони покривають блоки які підлягають зміні. Якщо не покривають або тестів немає — спочатку створи їх, перевір що вони покривають і відпрацьовують коректно, а потім роби рефакторинг і ще раз запускай тести, але якщо тести не відпрацьовують коректно після рефакторингу, то не роби рефакторинг.
|
|
56
|
+
|
|
46
57
|
Додай workflow `.github/workflows/lint-js.yml`:
|
|
47
58
|
|
|
48
59
|
```yaml
|
|
@@ -74,8 +85,12 @@ concurrency:
|
|
|
74
85
|
jobs:
|
|
75
86
|
eslint:
|
|
76
87
|
runs-on: ubuntu-latest
|
|
88
|
+
permissions:
|
|
89
|
+
contents: read
|
|
77
90
|
steps:
|
|
78
91
|
- uses: actions/checkout@v4
|
|
92
|
+
with:
|
|
93
|
+
persist-credentials: false
|
|
79
94
|
|
|
80
95
|
- uses: oven-sh/setup-bun@v2
|
|
81
96
|
|
package/mdc/js-pino.mdc
CHANGED
|
@@ -7,7 +7,7 @@ version: '1.0'
|
|
|
7
7
|
Проект використовує @nitra/pino для логування.
|
|
8
8
|
Якщо в проекті присутній @nitra/bunyan, то він повинен бути замінений на @nitra/pino.
|
|
9
9
|
|
|
10
|
-
В k8s/base/configmap.yaml повинен бути заданий OTEL_RESOURCE_ATTRIBUTES: 'service.name=<project_name>,service.namespace=<project_namespace>'
|
|
10
|
+
В **/k8s/base/configmap.yaml повинен бути заданий OTEL_RESOURCE_ATTRIBUTES: 'service.name=<project_name>,service.namespace=<project_namespace>'
|
|
11
11
|
а в директоріях з kustomize повинні бути перевизначені значення OTEL_RESOURCE_ATTRIBUTES і в них service.namespace повинен відповідати namespace, в якому знаходиться дана директорія.
|
|
12
12
|
|
|
13
13
|
Configmap повинен бути з тією самою назвою, що і deployment, якщо у deployment тільки один configmap.
|
package/mdc/npm-module.mdc
CHANGED
|
@@ -13,6 +13,52 @@ version: '1.0'
|
|
|
13
13
|
- npm/ - файли для npm модуля
|
|
14
14
|
- package.json
|
|
15
15
|
|
|
16
|
+
## npm publish (GitHub Actions)
|
|
17
|
+
|
|
18
|
+
У `.github/workflows/` має бути workflow `npm-publish.yml`, який публікує пакет з `npm/package.json` на npm при push у `main`, коли змінюється дерево `npm/**`.
|
|
19
|
+
|
|
20
|
+
Приклад вмісту:
|
|
21
|
+
|
|
22
|
+
```yaml title=".github/workflows/npm-publish.yml"
|
|
23
|
+
name: npm-publish
|
|
24
|
+
|
|
25
|
+
on:
|
|
26
|
+
push:
|
|
27
|
+
paths:
|
|
28
|
+
- 'npm/**'
|
|
29
|
+
branches:
|
|
30
|
+
- main
|
|
31
|
+
|
|
32
|
+
concurrency:
|
|
33
|
+
group: ${{ github.ref }}-${{ github.workflow }}
|
|
34
|
+
cancel-in-progress: true
|
|
35
|
+
|
|
36
|
+
jobs:
|
|
37
|
+
publish:
|
|
38
|
+
runs-on: ubuntu-latest
|
|
39
|
+
permissions:
|
|
40
|
+
contents: read
|
|
41
|
+
id-token: write # КРИТИЧНО для OIDC!
|
|
42
|
+
|
|
43
|
+
steps:
|
|
44
|
+
- uses: actions/checkout@v4
|
|
45
|
+
with:
|
|
46
|
+
persist-credentials: false
|
|
47
|
+
|
|
48
|
+
- uses: actions/setup-node@v5
|
|
49
|
+
with:
|
|
50
|
+
node-version: '24' # includes npm@11.6.0
|
|
51
|
+
registry-url: 'https://registry.npmjs.org'
|
|
52
|
+
|
|
53
|
+
- name: Publish package
|
|
54
|
+
uses: JS-DevTools/npm-publish@v4
|
|
55
|
+
with:
|
|
56
|
+
package: npm/package.json
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
- `id-token: write` потрібен для **trusted publishing** (OIDC) на npm, якщо пакет налаштований у npm без класичного токена в секретах.
|
|
60
|
+
- Шлях `package: npm/package.json` узгоджений зі структурою monorepo (`npm/` — каталог модуля).
|
|
61
|
+
|
|
16
62
|
## Перевірка
|
|
17
63
|
|
|
18
64
|
`npx @nitra/cursor check npm-module`
|
package/mdc/text.mdc
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Обробка та перевірка текстових файлів (cspell, markdownlint-cli2, v8r, CI)
|
|
3
3
|
alwaysApply: true
|
|
4
|
-
version: '1.
|
|
4
|
+
version: '1.13'
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
Правило описує роботу з **текстовими файлами** в репозиторії: перевірка правопису через **cspell**, стиль **Markdown** через **markdownlint-cli2**, валідація **JSON/YAML/TOML** через **[v8r](https://chris48s.github.io/v8r/)** ([Schema Store](https://www.schemastore.org/)), розширення **DavidAnson.vscode-markdownlint** у Cursor/VS Code та інтеграція в CI.
|
|
@@ -118,8 +118,12 @@ concurrency:
|
|
|
118
118
|
jobs:
|
|
119
119
|
text:
|
|
120
120
|
runs-on: ubuntu-latest
|
|
121
|
+
permissions:
|
|
122
|
+
contents: read
|
|
121
123
|
steps:
|
|
122
124
|
- uses: actions/checkout@v4
|
|
125
|
+
with:
|
|
126
|
+
persist-credentials: false
|
|
123
127
|
|
|
124
128
|
- uses: oven-sh/setup-bun@v2
|
|
125
129
|
|
|
@@ -206,9 +210,15 @@ jobs:
|
|
|
206
210
|
|
|
207
211
|
Підлаштуй `language` під проєкт (наприклад додай `ru-ru`, якщо потрібна перевірка російською). Порядок у `import` може впливати на пріоритет словників — тримай корпоративний `@nitra/cspell-dict` там, де зручно для ваших правил.
|
|
208
212
|
|
|
209
|
-
## Локальні виключення
|
|
213
|
+
## Локальні виключення (cspell `words`)
|
|
214
|
+
|
|
215
|
+
Коли **cspell** підсвічує слово, спочатку **виправ текст**, а не розширюй словник:
|
|
216
|
+
|
|
217
|
+
- виправ **друкарські помилки** та неправильні форми;
|
|
218
|
+
- **перефразуй коректною українською** (або англійською, залежно від контексту файлу): заміни кальки й випадкові склади на звичні формулювання зі словників;
|
|
219
|
+
- заміни **жаргон**, якщо є природний еквівалент у тому ж стилі документації (наприклад, у коментарях пиши «функція зворотного виклику» замість розмовного запозичення з англійського `callback`).
|
|
210
220
|
|
|
211
|
-
У
|
|
221
|
+
У секцію `words` у `.cspell.json` додавай записи **лише якщо переписати коректно неможливо** або **недоречно**: власні назви, стабільні технічні терміни без усталеного перекладу в проєкті, ідентифікатори зовнішніх API тощо.
|
|
212
222
|
|
|
213
223
|
## Інші мови
|
|
214
224
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nitra/cursor",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.5",
|
|
4
4
|
"description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"files": [
|
|
26
26
|
"mdc",
|
|
27
27
|
"bin",
|
|
28
|
+
"schemas",
|
|
28
29
|
"scripts",
|
|
29
30
|
"skills",
|
|
30
31
|
"AGENTS.template.md"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://unpkg.com/@nitra/cursor/schemas/n-cursor.json",
|
|
4
|
+
"title": "n-cursor project config",
|
|
5
|
+
"description": "Конфігурація правил і skills для CLI @nitra/cursor (файл .n-cursor.json у корені репозиторію).",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": true,
|
|
8
|
+
"properties": {
|
|
9
|
+
"rules": {
|
|
10
|
+
"type": "array",
|
|
11
|
+
"description": "Ідентифікатори правил без префікса n- (відповідають файлам n-<id>.mdc).",
|
|
12
|
+
"items": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"minLength": 1
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"skills": {
|
|
18
|
+
"type": "array",
|
|
19
|
+
"description": "Ідентифікатори skills без префікса n- (каталог .cursor/skills).",
|
|
20
|
+
"items": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"minLength": 1
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"required": ["rules", "skills"]
|
|
27
|
+
}
|
package/scripts/check-ga.mjs
CHANGED
|
@@ -30,7 +30,7 @@ export async function check() {
|
|
|
30
30
|
pass('Всі workflows мають розширення .yml')
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
for (const f of ['clean-ga-workflows.yml', 'clean-merged-branch.yml']) {
|
|
33
|
+
for (const f of ['clean-ga-workflows.yml', 'clean-merged-branch.yml', 'lint-ga.yml']) {
|
|
34
34
|
if (files.includes(f)) {
|
|
35
35
|
pass(`${f} існує`)
|
|
36
36
|
} else {
|
|
@@ -33,9 +33,7 @@ export async function check() {
|
|
|
33
33
|
fail('lint-js має викликати jscpd — додай "&& bunx jscpd ." у кінець скрипта')
|
|
34
34
|
}
|
|
35
35
|
} else {
|
|
36
|
-
fail(
|
|
37
|
-
'package.json не містить скрипт "lint-js" — додай: "oxlint --fix && bunx eslint --fix . && bunx jscpd ."'
|
|
38
|
-
)
|
|
36
|
+
fail('package.json не містить скрипт "lint-js" — додай: "oxlint --fix && bunx eslint --fix . && bunx jscpd ."')
|
|
39
37
|
}
|
|
40
38
|
|
|
41
39
|
if (pkg.devDependencies?.['@nitra/eslint-config']) {
|
|
@@ -1,10 +1,51 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
2
|
import { readFile } from 'node:fs/promises'
|
|
3
|
+
import { join } from 'node:path'
|
|
3
4
|
|
|
4
5
|
import { pass } from './utils/pass.mjs'
|
|
6
|
+
import { getMonorepoPackageRootDirs } from './utils/workspaces.mjs'
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
|
-
* Перевіряє відповідність
|
|
9
|
+
* Перевіряє відповідність правилам js-pino.mdc для одного workspace-пакета.
|
|
10
|
+
* @param {string} rootDir відносний шлях workspace (не `'.'`)
|
|
11
|
+
* @param {(msg: string) => void} fail функція зворотного виклику для реєстрації помилки перевірки
|
|
12
|
+
* @returns {Promise<void>} завершується після перевірок цього пакета
|
|
13
|
+
*/
|
|
14
|
+
async function checkWorkspacePackage(rootDir, fail) {
|
|
15
|
+
const label = `[${rootDir}] `
|
|
16
|
+
const pkgPath = join(rootDir, 'package.json')
|
|
17
|
+
if (existsSync(pkgPath)) {
|
|
18
|
+
const pkg = JSON.parse(await readFile(pkgPath, 'utf8'))
|
|
19
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies }
|
|
20
|
+
|
|
21
|
+
if (allDeps['@nitra/bunyan']) {
|
|
22
|
+
fail(`${label}@nitra/bunyan знайдено — замінити на @nitra/pino`)
|
|
23
|
+
}
|
|
24
|
+
if (allDeps.bunyan) {
|
|
25
|
+
fail(`${label}bunyan знайдено — замінити на @nitra/pino`)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const configmapPath = join(rootDir, 'k8s/base/configmap.yaml')
|
|
30
|
+
if (existsSync(configmapPath)) {
|
|
31
|
+
const content = await readFile(configmapPath, 'utf8')
|
|
32
|
+
if (content.includes('OTEL_RESOURCE_ATTRIBUTES')) {
|
|
33
|
+
pass(`${label}k8s/base/configmap.yaml містить OTEL_RESOURCE_ATTRIBUTES`)
|
|
34
|
+
if (content.includes('service.name=') && content.includes('service.namespace=')) {
|
|
35
|
+
pass(`${label}OTEL_RESOURCE_ATTRIBUTES містить service.name та service.namespace`)
|
|
36
|
+
} else {
|
|
37
|
+
fail(
|
|
38
|
+
`${label}OTEL_RESOURCE_ATTRIBUTES має містити service.name=<name>,service.namespace=<namespace>`
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
fail(`${label}k8s/base/configmap.yaml не містить OTEL_RESOURCE_ATTRIBUTES`)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Перевіряє відповідність проєкту правилам js-pino.mdc лише для workspace-пакетів (не корінь репо).
|
|
8
49
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
9
50
|
*/
|
|
10
51
|
export async function check() {
|
|
@@ -14,26 +55,18 @@ export async function check() {
|
|
|
14
55
|
exitCode = 1
|
|
15
56
|
}
|
|
16
57
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies }
|
|
58
|
+
const roots = await getMonorepoPackageRootDirs()
|
|
59
|
+
const workspaceRoots = roots.filter(r => r !== '.')
|
|
20
60
|
|
|
21
|
-
|
|
22
|
-
|
|
61
|
+
if (workspaceRoots.length === 0) {
|
|
62
|
+
pass(
|
|
63
|
+
'js-pino: немає workspace-пакетів у кореневому package.json — перевірку залежностей і k8s у пакетах пропущено'
|
|
64
|
+
)
|
|
65
|
+
return exitCode
|
|
23
66
|
}
|
|
24
67
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (content.includes('OTEL_RESOURCE_ATTRIBUTES')) {
|
|
28
|
-
pass('k8s/base/configmap.yaml містить OTEL_RESOURCE_ATTRIBUTES')
|
|
29
|
-
if (content.includes('service.name=') && content.includes('service.namespace=')) {
|
|
30
|
-
pass('OTEL_RESOURCE_ATTRIBUTES містить service.name та service.namespace')
|
|
31
|
-
} else {
|
|
32
|
-
fail('OTEL_RESOURCE_ATTRIBUTES має містити service.name=<name>,service.namespace=<namespace>')
|
|
33
|
-
}
|
|
34
|
-
} else {
|
|
35
|
-
fail('k8s/base/configmap.yaml не містить OTEL_RESOURCE_ATTRIBUTES')
|
|
36
|
-
}
|
|
68
|
+
for (const r of workspaceRoots) {
|
|
69
|
+
await checkWorkspacePackage(r, fail)
|
|
37
70
|
}
|
|
38
71
|
|
|
39
72
|
return exitCode
|
package/scripts/check-vue.mjs
CHANGED
|
@@ -1,10 +1,98 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
2
|
import { readFile } from 'node:fs/promises'
|
|
3
|
+
import { join } from 'node:path'
|
|
3
4
|
|
|
4
5
|
import { pass } from './utils/pass.mjs'
|
|
6
|
+
import { getMonorepoPackageRootDirs } from './utils/workspaces.mjs'
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
|
-
*
|
|
9
|
+
* Формує зрозумілий для людини підпис пакета для повідомлень перевірки.
|
|
10
|
+
* @param {string} rootDir відносний шлях (`'.'` або `site` тощо)
|
|
11
|
+
* @returns {string} підпис для логів перевірки
|
|
12
|
+
*/
|
|
13
|
+
function packageLabel(rootDir) {
|
|
14
|
+
return rootDir === '.' ? 'корінь' : rootDir
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Перевіряє залежності та vite.config одного Vue-пакета.
|
|
19
|
+
* @param {string} rootDir відносний шлях до пакета
|
|
20
|
+
* @param {(msg: string) => void} fail функція зворотного виклику для реєстрації помилки перевірки
|
|
21
|
+
* @returns {Promise<void>} завершується після перевірок залежностей і Vite
|
|
22
|
+
*/
|
|
23
|
+
async function checkVuePackage(rootDir, fail) {
|
|
24
|
+
const label = packageLabel(rootDir)
|
|
25
|
+
const prefix = `[${label}] `
|
|
26
|
+
|
|
27
|
+
const pkgPath = join(rootDir, 'package.json')
|
|
28
|
+
const pkg = JSON.parse(await readFile(pkgPath, 'utf8'))
|
|
29
|
+
const deps = pkg.dependencies || {}
|
|
30
|
+
const devDeps = pkg.devDependencies || {}
|
|
31
|
+
const allDeps = { ...deps, ...devDeps }
|
|
32
|
+
|
|
33
|
+
if (deps.vue) {
|
|
34
|
+
pass(`${prefix}vue в dependencies: ${deps.vue}`)
|
|
35
|
+
} else {
|
|
36
|
+
fail(`${prefix}vue відсутній в dependencies`)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (devDeps.vite) {
|
|
40
|
+
const match = devDeps.vite.match(/(\d+)/)
|
|
41
|
+
if (match && Number(match[1]) >= 8) {
|
|
42
|
+
pass(`${prefix}vite >= 8: ${devDeps.vite}`)
|
|
43
|
+
} else {
|
|
44
|
+
fail(`${prefix}vite має бути >= 8, знайдено: ${devDeps.vite}`)
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
fail(`${prefix}vite відсутній в devDependencies`)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (devDeps['@vitejs/plugin-vue']) {
|
|
51
|
+
pass(`${prefix}@vitejs/plugin-vue: ${devDeps['@vitejs/plugin-vue']}`)
|
|
52
|
+
} else {
|
|
53
|
+
fail(`${prefix}@vitejs/plugin-vue відсутній в devDependencies`)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (allDeps['vue-macros']) {
|
|
57
|
+
pass(`${prefix}vue-macros: ${allDeps['vue-macros']}`)
|
|
58
|
+
} else {
|
|
59
|
+
fail(`${prefix}vue-macros відсутній — bun add -d vue-macros`)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (allDeps['unplugin-auto-import']) {
|
|
63
|
+
pass(`${prefix}unplugin-auto-import присутній`)
|
|
64
|
+
} else {
|
|
65
|
+
fail(`${prefix}unplugin-auto-import відсутній — bun add -d unplugin-auto-import`)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (allDeps['vite-plugin-vue-layouts-next']) {
|
|
69
|
+
pass(`${prefix}vite-plugin-vue-layouts-next присутній`)
|
|
70
|
+
} else {
|
|
71
|
+
fail(`${prefix}vite-plugin-vue-layouts-next відсутній — bun add -d vite-plugin-vue-layouts-next`)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const configFiles = ['vite.config.js', 'vite.config.ts', 'vite.config.mjs']
|
|
75
|
+
const viteConfig = configFiles.find(f => existsSync(join(rootDir, f)))
|
|
76
|
+
if (viteConfig) {
|
|
77
|
+
const relConfig = join(rootDir, viteConfig)
|
|
78
|
+
const content = await readFile(relConfig, 'utf8')
|
|
79
|
+
if (content.includes('VueMacros')) {
|
|
80
|
+
pass(`${prefix}${viteConfig} використовує VueMacros`)
|
|
81
|
+
} else {
|
|
82
|
+
fail(`${prefix}${viteConfig} не містить VueMacros`)
|
|
83
|
+
}
|
|
84
|
+
if (content.includes('AutoImport')) {
|
|
85
|
+
pass(`${prefix}${viteConfig} використовує AutoImport`)
|
|
86
|
+
} else {
|
|
87
|
+
fail(`${prefix}${viteConfig} не містить AutoImport`)
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
fail(`${prefix}немає vite.config.js|ts|mjs у каталозі пакета`)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Перевіряє відповідність проєкту правилам vue.mdc (корінь і всі workspace-пакети з `vue` у dependencies).
|
|
8
96
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
9
97
|
*/
|
|
10
98
|
export async function check() {
|
|
@@ -25,70 +113,26 @@ export async function check() {
|
|
|
25
113
|
fail('.vscode/extensions.json не існує')
|
|
26
114
|
}
|
|
27
115
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
} else {
|
|
37
|
-
fail('vue відсутній в dependencies')
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (devDeps.vite) {
|
|
41
|
-
const match = devDeps.vite.match(/(\d+)/)
|
|
42
|
-
if (match && Number(match[1]) >= 8) {
|
|
43
|
-
pass(`vite >= 8: ${devDeps.vite}`)
|
|
44
|
-
} else {
|
|
45
|
-
fail(`vite має бути >= 8, знайдено: ${devDeps.vite}`)
|
|
46
|
-
}
|
|
47
|
-
} else {
|
|
48
|
-
fail('vite відсутній в devDependencies')
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (devDeps['@vitejs/plugin-vue']) {
|
|
52
|
-
pass(`@vitejs/plugin-vue: ${devDeps['@vitejs/plugin-vue']}`)
|
|
53
|
-
} else {
|
|
54
|
-
fail('@vitejs/plugin-vue відсутній в devDependencies')
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (allDeps['vue-macros']) {
|
|
58
|
-
pass(`vue-macros: ${allDeps['vue-macros']}`)
|
|
59
|
-
} else {
|
|
60
|
-
fail('vue-macros відсутній — bun add -d vue-macros')
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (allDeps['unplugin-auto-import']) {
|
|
64
|
-
pass('unplugin-auto-import присутній')
|
|
65
|
-
} else {
|
|
66
|
-
fail('unplugin-auto-import відсутній — bun add -d unplugin-auto-import')
|
|
116
|
+
const roots = await getMonorepoPackageRootDirs()
|
|
117
|
+
/** @type {string[]} */
|
|
118
|
+
const vueRoots = []
|
|
119
|
+
for (const r of roots) {
|
|
120
|
+
const p = join(r, 'package.json')
|
|
121
|
+
if (existsSync(p)) {
|
|
122
|
+
const pkg = JSON.parse(await readFile(p, 'utf8'))
|
|
123
|
+
if (pkg.dependencies?.vue) vueRoots.push(r)
|
|
67
124
|
}
|
|
125
|
+
}
|
|
68
126
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
127
|
+
if (vueRoots.length === 0) {
|
|
128
|
+
fail(
|
|
129
|
+
'vue не знайдено в dependencies жодного пакета (корінь репо та каталоги з кореневого workspaces)'
|
|
130
|
+
)
|
|
131
|
+
return exitCode
|
|
74
132
|
}
|
|
75
133
|
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
if (viteConfig) {
|
|
79
|
-
const content = await readFile(viteConfig, 'utf8')
|
|
80
|
-
if (content.includes('VueMacros')) {
|
|
81
|
-
pass('vite.config використовує VueMacros')
|
|
82
|
-
} else {
|
|
83
|
-
fail(`${viteConfig} не містить VueMacros`)
|
|
84
|
-
}
|
|
85
|
-
if (content.includes('AutoImport')) {
|
|
86
|
-
pass('vite.config використовує AutoImport')
|
|
87
|
-
} else {
|
|
88
|
-
fail(`${viteConfig} не містить AutoImport`)
|
|
89
|
-
}
|
|
90
|
-
} else {
|
|
91
|
-
fail('vite.config.js не існує')
|
|
134
|
+
for (const r of vueRoots) {
|
|
135
|
+
await checkVuePackage(r, fail)
|
|
92
136
|
}
|
|
93
137
|
|
|
94
138
|
return exitCode
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { glob, readFile } from 'node:fs/promises'
|
|
3
|
+
import { dirname, join, relative } from 'node:path'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Нормалізує поле `workspaces` з package.json до масиву шляхів / glob-патернів.
|
|
7
|
+
* @param {unknown} workspaces значення `workspaces` з кореневого package.json
|
|
8
|
+
* @returns {string[]} масив патернів workspaces
|
|
9
|
+
*/
|
|
10
|
+
export function normalizeWorkspacePatterns(workspaces) {
|
|
11
|
+
if (!workspaces) return []
|
|
12
|
+
if (Array.isArray(workspaces)) return workspaces
|
|
13
|
+
if (typeof workspaces === 'object' && workspaces !== null && Array.isArray(workspaces.packages)) {
|
|
14
|
+
return workspaces.packages
|
|
15
|
+
}
|
|
16
|
+
return []
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Повертає каталоги з `package.json`: корінь репозиторію та всі пакети з `workspaces`.
|
|
21
|
+
* @param {string} repoRoot зазвичай `process.cwd()`
|
|
22
|
+
* @returns {Promise<string[]>} відносні шляхи до коренів пакетів; `'.'` першим, без дублікатів
|
|
23
|
+
*/
|
|
24
|
+
export async function getMonorepoPackageRootDirs(repoRoot = '.') {
|
|
25
|
+
const roots = new Set(['.'])
|
|
26
|
+
const rootPkgPath = join(repoRoot, 'package.json')
|
|
27
|
+
if (!existsSync(rootPkgPath)) {
|
|
28
|
+
return ['.']
|
|
29
|
+
}
|
|
30
|
+
const pkg = JSON.parse(await readFile(rootPkgPath, 'utf8'))
|
|
31
|
+
for (const raw of normalizeWorkspacePatterns(pkg.workspaces)) {
|
|
32
|
+
const w = raw.replaceAll('\\', '/').replace(/\/+$/, '') || '.'
|
|
33
|
+
if (w.includes('*')) {
|
|
34
|
+
const globPat = `${w}/package.json`
|
|
35
|
+
for await (const f of glob(globPat, { cwd: repoRoot })) {
|
|
36
|
+
const abs = join(repoRoot, f)
|
|
37
|
+
const rel = relative(repoRoot, dirname(abs))
|
|
38
|
+
roots.add(rel === '' ? '.' : rel)
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
const pkgJson = join(repoRoot, w, 'package.json')
|
|
42
|
+
if (existsSync(pkgJson)) roots.add(w)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const list = [...roots]
|
|
46
|
+
list.sort((a, b) => {
|
|
47
|
+
if (a === '.') return -1
|
|
48
|
+
if (b === '.') return 1
|
|
49
|
+
return a.localeCompare(b)
|
|
50
|
+
})
|
|
51
|
+
return list
|
|
52
|
+
}
|