@nitra/cursor 1.5.3 → 1.6.4

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/bun.mdc CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: Bun як єдиний package manager у монорепо
3
3
  alwaysApply: true
4
- version: '1.1'
4
+ version: '1.2'
5
5
  ---
6
6
 
7
7
  Проект використовує тільки Bun для керування залежностями та запуску скриптів.
@@ -50,7 +50,7 @@ Lockfile у репозиторії: `bun.lock`.
50
50
 
51
51
  Якщо в package.json є поля `packageManager`, то прибрати їх, також прибрати всі директорії та файли для yarn
52
52
 
53
- Якщо в проекті використовується npx, то не заміняти його на bunx, а використовувати npx.
53
+ Якщо в проекті використовується npx, то не замінювати його на bunx, а використовувати npx.
54
54
  Коли зміна відбувається в Dockerfile, то використовувати
55
55
 
56
56
  ```dockerfile
package/mdc/ga.mdc CHANGED
@@ -1,30 +1,10 @@
1
1
  ---
2
2
  description: Правила форматів для .github/workflows
3
3
  alwaysApply: true
4
- version: '1.0'
4
+ version: '1.2'
5
5
  ---
6
6
 
7
- #
8
-
9
- Всі файли в директорії .github/workflows повинні мати розширення тільки yml
10
-
11
- Якщо є .github/workflows/apply-k8s.yml, то в ньому обов'язково повинно бути вказано що критерій для запуску це:
12
-
13
- ```yaml
14
- on:
15
- push:
16
- paths:
17
- - '**/k8s/*.yaml'
18
- ```
19
-
20
- Якщо є .github/workflows/apply-nats-consumer.yml, то в ньому обов'язково повинно бути вказано що критерій для запуску це:
21
-
22
- ```yaml
23
- on:
24
- push:
25
- paths:
26
- - '**/consumer.yaml'
27
- ```
7
+ У `.github/workflows/` лише **`.yml`**. Мають бути **`clean-ga-workflows.yml`**, **`clean-merged-branch.yml`**, **`lint-ga.yml`**. Якщо є **`apply-k8s.yml`** / **`apply-nats-consumer.yml`** — paths у тригері як у фрагментах.
28
8
 
29
9
  Повинен бути файл .github/workflows/clean-ga-workflows.yml, зі змістом:
30
10
 
@@ -53,7 +33,6 @@ jobs:
53
33
  save_min_runs_number: 0
54
34
 
55
35
  ```
56
-
57
36
  Повинен бути файл .github/workflows/clean-merged-branch.yml, зі змістом:
58
37
 
59
38
  ```yaml
@@ -89,8 +68,7 @@ jobs:
89
68
  echo "Deleted branches: ${DELETED_BRANCHES}"
90
69
  ```
91
70
 
92
- якщо в ignore_branches задані інші бранчі, то це допустимо.
93
-
71
+ Інші гілки в `ignore_branches` допустимо.
94
72
  Повинен бути файл .github/workflows/lint-ga.yml, зі змістом:
95
73
 
96
74
  ```yaml
@@ -145,25 +123,13 @@ jobs:
145
123
  run: bun run lint-ga
146
124
  ```
147
125
 
148
- в файлі .vscode/extensions.json є налаштування для GitHub Actions:
149
-
150
126
  ```json title=".vscode/extensions.json"
151
127
  {
152
128
  "recommendations": ["github.vscode-github-actions"]
153
129
  }
154
130
  ```
155
131
 
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` має бути скрипт:
132
+ **Лінт:** [actionlint](https://github.com/rhysd/actionlint) через [node-actionlint](https://www.npmjs.com/package/node-actionlint); [zizmor](https://docs.zizmor.sh) — `uvx`, офлайн. Скрипт у корені:
167
133
 
168
134
  ```json title="package.json"
169
135
  "scripts": {
@@ -171,11 +137,22 @@ jobs:
171
137
  }
172
138
  ```
173
139
 
174
- Параметр `--offline` обмежує аналіз офлайн-аудитами (без GitHub API);
140
+ **`.github/zizmor.yml`:** для [unpinned-uses](https://docs.zizmor.sh/audits/#unpinned-uses) — політика **`ref-pin`**, якщо в `uses:` семантичні теги. За потреби вимкни [template-injection](https://docs.zizmor.sh/audits/#template-injection):
141
+
142
+ ```yaml title=".github/zizmor.yml"
143
+ # https://docs.zizmor.sh/configuration/
144
+ rules:
145
+ unpinned-uses:
146
+ config:
147
+ policies:
148
+ '*': ref-pin
149
+ template-injection:
150
+ disable: true
151
+ ```
175
152
 
176
- За замовчуванням audit [unpinned-uses](https://docs.zizmor.sh/audits/#unpinned-uses) вимагає повний commit SHA для кожного `uses:`. У всих проєктах прийняті **семантичні теги** (`@v4`, `@v2` тощо), додай `.github/zizmor.yml` з політикою `ref-pin` (приклад у цьому репозиторії).
153
+ **MegaLinter:** не використовувати; прибрати workflow, конфіги (`.mega-linter.yml`, `.megalinter.yaml`, `.mega-linter.yaml`), залежності та згадки в CI / pre-commit / документації.
177
154
 
178
155
  ## Перевірка
179
156
 
180
- - `bun run lint-ga` — actionlint (node-actionlint) і zizmor
157
+ - `bun run lint-ga`
181
158
  - `npx @nitra/cursor check ga`
package/mdc/js-lint.mdc CHANGED
@@ -1,12 +1,10 @@
1
1
  ---
2
2
  description: Перевірка JavaScript коду
3
3
  alwaysApply: true
4
- version: '1.4'
4
+ version: '1.7'
5
5
  ---
6
6
 
7
- Перевірка виконується за допомогою **oxlint**, **ESLint** та **jscpd** (дублікати коду).
8
-
9
- У файлі `.vscode/extensions.json` мають бути рекомендації розширень:
7
+ **oxlint**, **ESLint**, **jscpd**. У скрипті **`lint-js`:** `oxlint` (без `bunx`), **`bunx eslint`**, **`bunx jscpd`**; у CI — `bunx oxlint` / `bunx eslint` / `bunx jscpd`. Без **prettier** і **@nitra/prettier-config**. Достатньо **`@nitra/eslint-config`** у devDependencies; пакети oxlint/eslint/jscpd не додавай без потреби монорепо.
10
8
 
11
9
  ```json title=".vscode/extensions.json"
12
10
  {
@@ -18,19 +16,19 @@ version: '1.4'
18
16
  }
19
17
  ```
20
18
 
21
- У кореневому `package.json` додай скрипт і залежності:
22
-
23
19
  ```json title="package.json"
20
+ {
24
21
  "scripts": {
25
22
  "lint-js": "oxlint --fix && bunx eslint --fix . && bunx jscpd ."
26
23
  },
27
24
  "devDependencies": {
28
25
  "@nitra/eslint-config": "^3.4.0"
26
+ },
27
+ "engines": {
28
+ "node": ">=24"
29
29
  }
30
+ }
30
31
  ```
31
-
32
- У `devDependencies` достатньо `@nitra/eslint-config` (залежності ESLint підтягуються разом із ним). **oxlint**, **eslint** та **jscpd** у скриптах викликай через `bunx`; окремо не додавай пакети `oxlint` / `eslint` / `jscpd`, якщо цього не вимагає ваш монорепо.
33
-
34
32
  У корені проєкту має бути `.jscpd.json`. Мінімум: увімкнути облік `.gitignore`, ненульовий код виходу при знаходженні клонів, консольний звіт. За потреби додай `ignore` (дзеркальні каталоги, шаблони) та `minLines`, щоб відсікти дрібні збіги:
35
33
 
36
34
  ```json title=".jscpd.json"
@@ -43,9 +41,20 @@ version: '1.4'
43
41
  }
44
42
  ```
45
43
 
46
- Додай workflow `.github/workflows/lint-js.yml`:
44
+ ## jscpd: рефакторинг і структура
45
+
46
+ Коли **jscpd** знаходить клони, спочатку зменшуй дублювання кодом, а не конфігом.
47
47
 
48
- ```yaml
48
+ - **Рефакторинг:** винеси спільні фрагменти в функції, модулі, утиліти, composables/hooks, спільні компоненти або базові типи — залежно від контексту.
49
+ - **Структура:** якщо одна й та сама логіка розмазана між файлами чи пакетами, запропонуй зміну структури (наприклад, спільний модуль, `shared/`, внутрішній пакет у monorepo), щоб була **одна канонічна реалізація** і повторні місця лише викликали її.
50
+
51
+ Так ти уникаєш **хибних обходів** перевірки: розширення `ignore` чи завищення `minLines` лише щоб прибрати звіт — не заміна рефакторингу для справжніх клонів. Якщо збіг **семантично випадковий** (генерований код, формальні шаблони без спільної логіки), після оцінки допустимо точковий `ignore` або зміна порогу — з коротким обґрунтуванням.
52
+
53
+ Але обов'язково перед рефакторингом перевір чи є тести на блоки які підлягають зміні, а саме Bun.test для js та playwright для vue. Якщо є, то перевір чи вони покривають блоки які підлягають зміні. Якщо не покривають або тестів немає — спочатку створи їх, перевір що вони покривають і відпрацьовують коректно, а потім роби рефакторинг і ще раз запускай тести, але якщо тести не відпрацьовують коректно після рефакторингу, то не роби рефакторинг.
54
+
55
+ Додай workflow:
56
+
57
+ ```yaml title=".github/workflows/lint-js.yml"
49
58
  name: Lint JS
50
59
 
51
60
  on:
@@ -103,9 +112,7 @@ jobs:
103
112
  bunx jscpd .
104
113
  ```
105
114
 
106
- **Без дублювання CI:** якщо в `.github/workflows` уже є `lint.yml` з тими самими кроками `oxlint` або `eslint`, видали зайвий workflow, а саме `lint.yml` лінт JS має виконуватися в одному місці.
107
-
108
- У корені проєкту має бути `eslint.config.js` із розподілом директорій за типом коду:
115
+ Один workflow на лінт JS; зайвий `lint.yml` з тими самими кроками — прибери.
109
116
 
110
117
  ```javascript title="eslint.config.js"
111
118
  import { getConfig } from '@nitra/eslint-config'
@@ -118,7 +125,6 @@ export default [
118
125
  ```
119
126
 
120
127
  У монорепо пакети з Vite (frontend) вкажи в секції `vue`, решту — у секції `node` у виклику `getConfig`.
121
-
122
128
  ## Додаткові js правила
123
129
 
124
130
  Завжди додавай до package.json що підтримується 24+ версія node:
@@ -128,10 +134,7 @@ export default [
128
134
  "node": ">=24"
129
135
  }
130
136
  ```
131
-
132
- Завжди код повинен використовувати синтаксис 24+ версії node
133
-
134
- Завжди повинен використовуватися top level await
137
+ **Код:** синтаксис Node **24+**, **top level await**.
135
138
 
136
139
  ## Перевірка
137
140
 
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.
@@ -1,17 +1,51 @@
1
1
  ---
2
2
  description: Оформлення репозиторію для npm модуля
3
3
  alwaysApply: true
4
- version: '1.0'
4
+ version: '1.2'
5
5
  ---
6
6
 
7
- Проект представляє собою Bun monorepo
7
+ Bun monorepo: workspace **`npm/`**, кореневий **`package.json`**, **`.github/workflows/`**; опційно **`demo/`**.
8
8
 
9
- Структура проекту:
9
+ ## npm publish
10
10
 
11
- - .github/workflows/ - скрипти для github actions
12
- - demo/ - опціонально демо проект
13
- - npm/ - файли для npm модуля
14
- - package.json
11
+ **`npm-publish.yml`:** push у **`main`**, **`paths: npm/**`**, **`JS-DevTools/npm-publish@v4`**, **`with.package: npm/package.json`**, **`permissions.id-token: write`** (OIDC на npm).
12
+
13
+ ```yaml title=".github/workflows/npm-publish.yml"
14
+ name: npm-publish
15
+
16
+ on:
17
+ push:
18
+ paths:
19
+ - 'npm/**'
20
+ branches:
21
+ - main
22
+
23
+ concurrency:
24
+ group: ${{ github.ref }}-${{ github.workflow }}
25
+ cancel-in-progress: true
26
+
27
+ jobs:
28
+ publish:
29
+ runs-on: ubuntu-latest
30
+ permissions:
31
+ contents: read
32
+ id-token: write # КРИТИЧНО для OIDC!
33
+
34
+ steps:
35
+ - uses: actions/checkout@v4
36
+ with:
37
+ persist-credentials: false
38
+
39
+ - uses: actions/setup-node@v5
40
+ with:
41
+ node-version: '24' # includes npm@11.6.0
42
+ registry-url: 'https://registry.npmjs.org'
43
+
44
+ - name: Publish package
45
+ uses: JS-DevTools/npm-publish@v4
46
+ with:
47
+ package: npm/package.json
48
+ ```
15
49
 
16
50
  ## Перевірка
17
51
 
package/mdc/text.mdc CHANGED
@@ -1,14 +1,10 @@
1
1
  ---
2
2
  description: Обробка та перевірка текстових файлів (cspell, markdownlint-cli2, v8r, CI)
3
3
  alwaysApply: true
4
- version: '1.12'
4
+ version: '1.16'
5
5
  ---
6
6
 
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.
8
-
9
- ## Розширення Cursor / VS Code
10
-
11
- У файлі `.vscode/extensions.json` **додай** `DavidAnson.vscode-markdownlint` у масив `recommendations` (разом із записами з інших правил, зокрема js-lint / js-format):
7
+ **cspell**, **markdownlint-cli2**, **[v8r](https://chris48s.github.io/v8r/)** ([Schema Store](https://www.schemastore.org/)), розширення **DavidAnson.vscode-markdownlint**, workflow **`lint-text`**.
12
8
 
13
9
  ```json title=".vscode/extensions.json"
14
10
  {
@@ -21,34 +17,20 @@ version: '1.12'
21
17
  }
22
18
  ```
23
19
 
24
- ## Скрипти та залежності в package.json
25
-
26
- У кореневому `package.json` один скрипт **lint-text** має послідовно викликати **cspell**, **markdownlint-cli2** і **v8r** (і решту блоків з розділів нижче в одному файлі):
20
+ **`package.json`:** скрипт **`lint-text`** і devDependencies **`@nitra/cspell-dict`**, **`markdownlint-cli2`**. Для української додай **`@cspell/dict-uk-ua`**. **`v8r`** лише через **`bunx`**, не в devDependencies. Окремий пакет **`markdownlint`** не потрібний.
27
21
 
28
22
  ```json title="package.json"
23
+ {
29
24
  "scripts": {
30
25
  "lint-text": "npx cspell . && bunx markdownlint-cli2 --fix \"**/*.md\" \"**/*.mdc\" && (bunx v8r \"**/*.json\" || [ $? -eq 98 ]) && (bunx v8r \"**/*.yml\" || [ $? -eq 98 ]) && (bunx v8r \"**/*.yaml\" || [ $? -eq 98 ]) && (bunx v8r \"**/*.toml\" || [ $? -eq 98 ])"
31
26
  },
32
27
  "devDependencies": {
33
- "markdownlint-cli2": "^0.22.0"
28
+ "@nitra/cspell-dict": "^1.0.185"
34
29
  }
30
+ }
35
31
  ```
36
32
 
37
- **markdownlint-cli2** і **v8r** у скрипті викликай через `bunx`. Окремо не додавай пакет `markdownlint`, якщо достатньо `markdownlint-cli2`. Маски `**/*.md` і `**/*.mdc` охоплюють Markdown і правила Cursor (`.mdc`); виключення з `.gitignore` для markdownlint задаються в `.markdownlint-cli2.jsonc` (`gitignore: true`). За потреби можна додати окремий скрипт `lint-md` лише для markdownlint CI все одно має викликати повний `bun run lint-text`.
38
-
39
- ## v8r
40
-
41
- [v8r](https://chris48s.github.io/v8r/) — CLI-валідатор: за іменем файлу знаходить схему в [Schema Store](https://www.schemastore.org/) і перевіряє вміст. У `lint-text` — чотири послідовні виклики з масками `**/*.json`, `**/*.yml`, `**/*.yaml`, `**/*.toml`, кожен у вигляді `(bunx v8r "<glob>" || [ $? -eq 98 ])`.
42
-
43
- **Код 98:** якщо для glob **немає жодного файлу**, v8r дає **98**. Це стосується не лише TOML: репозиторій без `.yml`/`.yaml` зламає **один** спільний виклик `v8r` уже на `**/*.yml`. Окремі виклики з дозволом лише **98** покривають і такі репозиторії.
44
-
45
- За замовчуванням v8r враховує **`.gitignore`**. **`.v8rignore` не створюй завчасно** — файл потрібен **лише коли** `bunx v8r` падає на JSON/YAML без схеми в каталозі (тоді додай у ignore відповідні glob’и).
46
-
47
- Пакет **`v8r` у `devDependencies` не додавай** — у скрипті достатньо **`bunx v8r`**.
48
-
49
- ## Конфігурація markdownlint-cli2
50
-
51
- У корені проєкту має бути **`.markdownlint-cli2.jsonc`** (документація: [DavidAnson/markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2)):
33
+ **v8r:** чотири виклики `(bunx v8r "<glob>" || [ $? -eq 98 ])` exit **98**, якщо glob порожній. **`.v8rignore`** лише коли немає схеми. Враховує `.gitignore`.
52
34
 
53
35
  ```json title=".markdownlint-cli2.jsonc"
54
36
  {
@@ -63,19 +45,14 @@ version: '1.12'
63
45
  }
64
46
  ```
65
47
 
66
- `gitignore: true` змушує markdownlint-cli2 пропускати шляхи з `.gitignore` тому числі `node_modules`, якщо воно там є). Додатковий glob `"#node_modules"` у скрипті не потрібен. За потреби додай власні шляхи через `ignores` у цьому ж файлі.
67
-
68
- За потреби увімкни окремі правила (наприклад `MD013` з `line_length: 120`), щоб узгодити довжину рядків з `.oxfmtrc.json`.
69
-
70
- `MD041` вимкнено навмисно: файли `.mdc` (Cursor rules) мають YAML frontmatter, після якого перший абзац часто не є заголовком H1.
48
+ **MD041** off навмисно (`.mdc` з frontmatter). Деталі [markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2).
71
49
 
72
50
  ## Cspell
73
51
 
74
52
  У корені проєкту має бути `.cspell.json` і залежності для cspell у кореневому `package.json` (зазвичай `devDependencies`).
75
53
 
76
54
  Додай workflow `.github/workflows/lint-text.yml`:
77
-
78
- ```yaml
55
+ ```yaml title=".github/workflows/lint-text.yml"
79
56
  name: Lint Text
80
57
 
81
58
  on:
@@ -144,11 +121,11 @@ jobs:
144
121
  run: bun run lint-text
145
122
  ```
146
123
 
147
- **Без дублювання CI:** якщо в `.github/workflows` уже є workflow з тими самими кроками `cspell` або `markdownlint-cli2`, видали зайвий — перевірки тексту та Markdown мають виконуватися в одному місці.
124
+ Не дублюй окремий workflow з тими самими кроками cspell/markdownlint.
148
125
 
149
- ## Базовий варіант cspell (без окремого словника української)
126
+ **`.cspell.json`**, `version: "0.2"`, **`language`**, **`import`** з `@nitra/cspell-dict`, **`ignorePaths`**, **`words`** лише для назв/термінів, коли не виправити текстом.
150
127
 
151
- Якщо текст переважно англійською та достатньо корпоративного словника (@nitra/cspell-dict; у полі `language` cspell лишається тег `nitra`):
128
+ Базово (англійська + українська + корпоративний словник):
152
129
 
153
130
  ```json title=".cspell.json"
154
131
  {
@@ -167,14 +144,14 @@ jobs:
167
144
  },
168
145
  "devDependencies": {
169
146
  "@nitra/cspell-dict": "^1.0.185",
170
- "markdownlint-cli2": "^0.22.0"
147
+ "@cspell/dict-uk-ua": "^4.0.6"
171
148
  }
172
149
  }
173
150
  ```
174
151
 
175
152
  ## Проєкт з українською мовою
176
153
 
177
- Якщо в репозиторії є українська документація, коментарі або рядки в коді — потрібен **окремий словник** `@cspell/dict-uk-ua`, інакше cspell не перевірятиме український правопис коректно.
154
+ Якщо в репозиторії є українська документація, коментарі або рядки в коді — потрібен **окремий словник** `@cspell/dict-ru_ru`, інакше cspell не перевірятиме російський правопис коректно.
178
155
 
179
156
  **1. Залежності** — додай пакет словника поруч із `@nitra/cspell-dict`:
180
157
 
@@ -186,6 +163,7 @@ jobs:
186
163
  "devDependencies": {
187
164
  "@nitra/cspell-dict": "^1.0.185",
188
165
  "@cspell/dict-uk-ua": "^4.0.6",
166
+ "@cspell/dict-ru_ru": "^2.3.2",
189
167
  "markdownlint-cli2": "^0.22.0"
190
168
  }
191
169
  }
@@ -210,14 +188,21 @@ jobs:
210
188
 
211
189
  Підлаштуй `language` під проєкт (наприклад додай `ru-ru`, якщо потрібна перевірка російською). Порядок у `import` може впливати на пріоритет словників — тримай корпоративний `@nitra/cspell-dict` там, де зручно для ваших правил.
212
190
 
213
- ## Локальні виключення
191
+ ## Локальні виключення (cspell `words`)
192
+
193
+ Коли **cspell** підсвічує слово, спочатку **виправ текст**, а не розширюй словник:
214
194
 
215
- У секції `words` у `.cspell.json` додають власні терміни, імена та скорочення, яких немає в словниках.
195
+ - виправ **друкарські помилки** та неправильні форми;
196
+ - **перефразуй коректною українською** (або англійською, залежно від контексту файлу): заміни кальки й випадкові склади на звичні формулювання зі словників;
197
+ - заміни **жаргон**, якщо є природний еквівалент у тому ж стилі документації (наприклад, у коментарях пиши «функція зворотного виклику» замість розмовного запозичення з англійського `callback`).
198
+
199
+ У секцію `words` у `.cspell.json` додавай записи **лише якщо переписати коректно неможливо** або **недоречно**: власні назви, стабільні технічні терміни без усталеного перекладу в проєкті, ідентифікатори зовнішніх API тощо.
216
200
 
217
201
  ## Інші мови
218
202
 
219
203
  Для іншої мови встанови відповідний пакет `@cspell/dict-*`, додай його `cspell-ext.json` у `import` і код мови в `language`. Огляд словників: [streetsidesoftware/cspell-dicts](https://github.com/streetsidesoftware/cspell-dicts).
220
204
 
205
+
221
206
  ## Перевірка
222
207
 
223
208
  `npx @nitra/cursor check text`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.5.3",
3
+ "version": "1.6.4",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Перевіряє відповідність репозиторію правилам Bun (n-bun.mdc).
3
+ *
4
+ * Очікує наявність `bun.lock`, забороняє lockfile та артефакти yarn/pnpm, директорію `.yarn`
5
+ * і поле `packageManager` у кореневому `package.json`.
6
+ */
1
7
  import { existsSync } from 'node:fs'
2
8
  import { readFile } from 'node:fs/promises'
3
9
 
@@ -1,8 +1,21 @@
1
+ /**
2
+ * Перевіряє GitHub Actions за правилом ga.mdc.
3
+ *
4
+ * Workflows лише з розширенням `.yml`, наявність clean/lint workflow, конфіг zizmor з ref-pin,
5
+ * відсутність MegaLinter, коректний скрипт `lint-ga` у `package.json` і виклик у `lint-ga.yml`.
6
+ */
1
7
  import { existsSync } from 'node:fs'
2
8
  import { readdir, readFile } from 'node:fs/promises'
9
+ import { join } from 'node:path'
3
10
 
4
11
  import { pass } from './utils/pass.mjs'
5
12
 
13
+ /** Шаблони наявності MegaLinter у вмісті workflow */
14
+ const MEGALINTER_USE_PATTERNS = [/oxsecurity\/megalinter-action/i, /megalinter\/megalinter/i]
15
+
16
+ /** Типові конфіги MegaLinter у корені репо */
17
+ const MEGALINTER_CONFIG_NAMES = ['.mega-linter.yml', '.megalinter.yaml', '.mega-linter.yaml']
18
+
6
19
  /**
7
20
  * Перевіряє відповідність проєкту правилам ga.mdc
8
21
  * @returns {Promise<number>} 0 — все OK, 1 — є проблеми
@@ -67,5 +80,76 @@ export async function check() {
67
80
  fail('.vscode/extensions.json не існує')
68
81
  }
69
82
 
83
+ const ymlWorkflows = files.filter(f => f.endsWith('.yml'))
84
+ let foundMegalinter = false
85
+ for (const f of ymlWorkflows) {
86
+ const content = await readFile(join(wfDir, f), 'utf8')
87
+ if (MEGALINTER_USE_PATTERNS.some(re => re.test(content))) {
88
+ foundMegalinter = true
89
+ fail(`MegaLinter у workflow ${wfDir}/${f} — видали інтеграцію (ga.mdc: MegaLinter)`)
90
+ }
91
+ }
92
+
93
+ for (const name of MEGALINTER_CONFIG_NAMES) {
94
+ if (existsSync(name)) {
95
+ foundMegalinter = true
96
+ fail(`Файл ${name} — видали конфіг MegaLinter (ga.mdc: MegaLinter)`)
97
+ }
98
+ }
99
+
100
+ if (!foundMegalinter) {
101
+ pass('Залишків MegaLinter не виявлено')
102
+ }
103
+
104
+ const zizmorPath = '.github/zizmor.yml'
105
+ if (existsSync(zizmorPath)) {
106
+ const z = await readFile(zizmorPath, 'utf8')
107
+ pass(`${zizmorPath} існує`)
108
+ if (z.includes('ref-pin')) {
109
+ pass(`${zizmorPath} містить політику ref-pin (zizmor)`)
110
+ } else {
111
+ fail(`${zizmorPath}: додай policies ref-pin для unpinned-uses (ga.mdc)`)
112
+ }
113
+ } else {
114
+ fail(`Відсутній ${zizmorPath} — потрібен для zizmor (ga.mdc)`)
115
+ }
116
+
117
+ if (existsSync('package.json')) {
118
+ const pkg = JSON.parse(await readFile('package.json', 'utf8'))
119
+ const lg = pkg.scripts?.['lint-ga']
120
+ if (typeof lg === 'string') {
121
+ pass('package.json містить lint-ga')
122
+ if (lg.includes('node-actionlint')) {
123
+ pass('lint-ga викликає node-actionlint')
124
+ } else {
125
+ fail('lint-ga має містити bunx node-actionlint (ga.mdc)')
126
+ }
127
+ if (lg.includes('zizmor') && lg.includes('--offline')) {
128
+ pass('lint-ga викликає zizmor з --offline')
129
+ } else {
130
+ fail('lint-ga має містити zizmor і --offline (ga.mdc)')
131
+ }
132
+ } else {
133
+ fail('package.json: додай скрипт "lint-ga" (ga.mdc)')
134
+ }
135
+ } else {
136
+ fail('package.json не існує — потрібен lint-ga у scripts')
137
+ }
138
+
139
+ const lintGaWf = join(wfDir, 'lint-ga.yml')
140
+ if (existsSync(lintGaWf)) {
141
+ const lgContent = await readFile(lintGaWf, 'utf8')
142
+ if (lgContent.includes('bun run lint-ga')) {
143
+ pass('lint-ga.yml викликає bun run lint-ga')
144
+ } else {
145
+ fail('lint-ga.yml: крок має містити bun run lint-ga')
146
+ }
147
+ if (lgContent.includes('astral-sh/setup-uv')) {
148
+ pass('lint-ga.yml містить astral-sh/setup-uv')
149
+ } else {
150
+ fail('lint-ga.yml: додай astral-sh/setup-uv для uvx zizmor (ga.mdc)')
151
+ }
152
+ }
153
+
70
154
  return exitCode
71
155
  }
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Перевіряє форматування коду за правилом js-format.mdc.
3
+ *
4
+ * `.oxfmtrc.json` з потрібними ключами, VSCode і oxfmt, відсутність Prettier у конфігах і залежностях.
5
+ */
1
6
  import { existsSync } from 'node:fs'
2
7
  import { readFile } from 'node:fs/promises'
3
8
 
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Перевіряє лінт JavaScript за правилом js-lint.mdc.
3
+ *
4
+ * Flat ESLint, скрипт `lint-js` (oxlint, eslint, jscpd), `engines.node`, без prettier,
5
+ * наявність `.jscpd.json` і workflow `lint-js.yml`.
6
+ */
1
7
  import { existsSync } from 'node:fs'
2
8
  import { readFile } from 'node:fs/promises'
3
9
 
@@ -27,15 +33,43 @@ export async function check() {
27
33
 
28
34
  if (pkg.scripts?.['lint-js']) {
29
35
  pass('package.json містить скрипт lint-js')
30
- if (String(pkg.scripts['lint-js']).includes('jscpd')) {
36
+ const lintJs = String(pkg.scripts['lint-js'])
37
+ if (lintJs.includes('jscpd')) {
31
38
  pass('lint-js містить jscpd')
32
39
  } else {
33
40
  fail('lint-js має викликати jscpd — додай "&& bunx jscpd ." у кінець скрипта')
34
41
  }
42
+ if (lintJs.includes('bunx eslint')) {
43
+ pass('lint-js викликає bunx eslint')
44
+ } else {
45
+ fail('lint-js має містити bunx eslint (n-js-lint.mdc)')
46
+ }
47
+ if (lintJs.includes('bunx jscpd')) {
48
+ pass('lint-js викликає bunx jscpd')
49
+ } else {
50
+ fail('lint-js має містити bunx jscpd (n-js-lint.mdc)')
51
+ }
52
+ if (lintJs.includes('oxlint')) {
53
+ pass('lint-js містить oxlint')
54
+ } else {
55
+ fail('lint-js має містити oxlint (n-js-lint.mdc)')
56
+ }
35
57
  } else {
36
58
  fail('package.json не містить скрипт "lint-js" — додай: "oxlint --fix && bunx eslint --fix . && bunx jscpd ."')
37
59
  }
38
60
 
61
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies }
62
+ if (allDeps.prettier) {
63
+ fail('package.json: видали залежність prettier (oxfmt замість prettier, n-js-lint.mdc)')
64
+ } else {
65
+ pass('package.json не містить prettier')
66
+ }
67
+ if (allDeps['@nitra/prettier-config']) {
68
+ fail('package.json: видали @nitra/prettier-config (n-js-lint.mdc)')
69
+ } else {
70
+ pass('package.json не містить @nitra/prettier-config')
71
+ }
72
+
39
73
  if (pkg.devDependencies?.['@nitra/eslint-config']) {
40
74
  pass('@nitra/eslint-config є в devDependencies')
41
75
  } else {
@@ -1,10 +1,55 @@
1
+ /**
2
+ * Для кожного workspace-пакета перевіряє правило js-pino.mdc.
3
+ *
4
+ * Заборона bunyan на користь pino та наявність `OTEL_RESOURCE_ATTRIBUTES` у `k8s/base/configmap.yaml`,
5
+ * якщо такий файл існує.
6
+ */
1
7
  import { existsSync } from 'node:fs'
2
8
  import { readFile } from 'node:fs/promises'
9
+ import { join } from 'node:path'
3
10
 
4
11
  import { pass } from './utils/pass.mjs'
12
+ import { getMonorepoPackageRootDirs } from './utils/workspaces.mjs'
13
+
14
+ /**
15
+ * Перевіряє відповідність правилам js-pino.mdc для одного workspace-пакета.
16
+ * @param {string} rootDir відносний шлях workspace (не `'.'`)
17
+ * @param {(msg: string) => void} fail функція зворотного виклику для реєстрації помилки перевірки
18
+ * @returns {Promise<void>} завершується після перевірок цього пакета
19
+ */
20
+ async function checkWorkspacePackage(rootDir, fail) {
21
+ const label = `[${rootDir}] `
22
+ const pkgPath = join(rootDir, 'package.json')
23
+ if (existsSync(pkgPath)) {
24
+ const pkg = JSON.parse(await readFile(pkgPath, 'utf8'))
25
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies }
26
+
27
+ if (allDeps['@nitra/bunyan']) {
28
+ fail(`${label}@nitra/bunyan знайдено — замінити на @nitra/pino`)
29
+ }
30
+ if (allDeps.bunyan) {
31
+ fail(`${label}bunyan знайдено — замінити на @nitra/pino`)
32
+ }
33
+ }
34
+
35
+ const configmapPath = join(rootDir, 'k8s/base/configmap.yaml')
36
+ if (existsSync(configmapPath)) {
37
+ const content = await readFile(configmapPath, 'utf8')
38
+ if (content.includes('OTEL_RESOURCE_ATTRIBUTES')) {
39
+ pass(`${label}k8s/base/configmap.yaml містить OTEL_RESOURCE_ATTRIBUTES`)
40
+ if (content.includes('service.name=') && content.includes('service.namespace=')) {
41
+ pass(`${label}OTEL_RESOURCE_ATTRIBUTES містить service.name та service.namespace`)
42
+ } else {
43
+ fail(`${label}OTEL_RESOURCE_ATTRIBUTES має містити service.name=<name>,service.namespace=<namespace>`)
44
+ }
45
+ } else {
46
+ fail(`${label}k8s/base/configmap.yaml не містить OTEL_RESOURCE_ATTRIBUTES`)
47
+ }
48
+ }
49
+ }
5
50
 
6
51
  /**
7
- * Перевіряє відповідність проєкту правилам js-pino.mdc
52
+ * Перевіряє відповідність проєкту правилам js-pino.mdc лише для workspace-пакетів (не корінь репо).
8
53
  * @returns {Promise<number>} 0 — все OK, 1 — є проблеми
9
54
  */
10
55
  export async function check() {
@@ -14,26 +59,16 @@ export async function check() {
14
59
  exitCode = 1
15
60
  }
16
61
 
17
- if (existsSync('package.json')) {
18
- const pkg = JSON.parse(await readFile('package.json', 'utf8'))
19
- const allDeps = { ...pkg.dependencies, ...pkg.devDependencies }
62
+ const roots = await getMonorepoPackageRootDirs()
63
+ const workspaceRoots = roots.filter(r => r !== '.')
20
64
 
21
- if (allDeps['@nitra/bunyan']) fail('@nitra/bunyan знайдено — замінити на @nitra/pino')
22
- if (allDeps.bunyan) fail('bunyan знайденозамінити на @nitra/pino')
65
+ if (workspaceRoots.length === 0) {
66
+ pass('js-pino: немає workspace-пакетів у кореневому package.json перевірку залежностей і k8s у пакетах пропущено')
67
+ return exitCode
23
68
  }
24
69
 
25
- if (existsSync('k8s/base/configmap.yaml')) {
26
- const content = await readFile('k8s/base/configmap.yaml', 'utf8')
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
- }
70
+ for (const r of workspaceRoots) {
71
+ await checkWorkspacePackage(r, fail)
37
72
  }
38
73
 
39
74
  return exitCode
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Перевіряє шаблон nginx за правилом nginx-default-tpl.mdc.
3
+ *
4
+ * Правильна назва файлу, `listen 8080`, `/healthz`, `gzip_static`, без `proxy_pass` у шаблоні,
5
+ * рекомендації VSCode для nginx.
6
+ */
1
7
  import { existsSync } from 'node:fs'
2
8
  import { readFile } from 'node:fs/promises'
3
9
 
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Перевіряє структуру npm-модуля в монорепо за правилом npm-module.mdc.
3
+ *
4
+ * Workspace `npm/`, `npm/package.json`, workflow `npm-publish.yml` з OIDC і шляхом до пакета.
5
+ */
1
6
  import { existsSync } from 'node:fs'
2
7
  import { readFile, stat } from 'node:fs/promises'
3
8
 
@@ -53,5 +58,28 @@ export async function check() {
53
58
  fail('.github/workflows/ не існує')
54
59
  }
55
60
 
61
+ const publishWf = '.github/workflows/npm-publish.yml'
62
+ if (existsSync(publishWf)) {
63
+ pass(`${publishWf} існує`)
64
+ const pub = await readFile(publishWf, 'utf8')
65
+ const need = [
66
+ { sub: 'npm/**', msg: `${publishWf}: у on.push.paths має бути npm/**` },
67
+ { sub: 'branches:', msg: `${publishWf}: очікується on.push.branches` },
68
+ { sub: 'main', msg: `${publishWf}: очікується branch main` },
69
+ { sub: 'id-token: write', msg: `${publishWf}: permissions має містити id-token: write (OIDC)` },
70
+ { sub: 'JS-DevTools/npm-publish', msg: `${publishWf}: очікується uses: JS-DevTools/npm-publish` },
71
+ { sub: 'package: npm/package.json', msg: `${publishWf}: with.package має бути npm/package.json` }
72
+ ]
73
+ for (const { sub, msg } of need) {
74
+ if (pub.includes(sub)) {
75
+ pass(`${publishWf} містить «${sub}»`)
76
+ } else {
77
+ fail(msg)
78
+ }
79
+ }
80
+ } else {
81
+ fail(`Відсутній ${publishWf} (npm-module.mdc: npm publish)`)
82
+ }
83
+
56
84
  return exitCode
57
85
  }
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Перевіряє CSS/SCSS лінт за правилом style-lint.mdc.
3
+ *
4
+ * `@nitra/stylelint-config`, скрипт `lint-style`, `.stylelintignore`, workflow `lint-style.yml`,
5
+ * VSCode stylelint і вимкнена вбудована CSS-валідація.
6
+ */
1
7
  import { existsSync } from 'node:fs'
2
8
  import { readFile } from 'node:fs/promises'
3
9
 
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Перевіряє текстовий стек за правилом text.mdc.
3
+ *
4
+ * cspell, markdownlint-cli2, скрипт `lint-text` з чотирма викликами v8r, workflow `lint-text.yml`,
5
+ * розширення VSCode для markdownlint.
6
+ */
1
7
  import { existsSync } from 'node:fs'
2
8
  import { readFile } from 'node:fs/promises'
3
9
 
@@ -90,7 +96,7 @@ export async function check() {
90
96
  if (devDeps['markdownlint-cli2']) {
91
97
  pass('markdownlint-cli2 є в devDependencies')
92
98
  } else {
93
- fail('markdownlint-cli2 відсутній — bun add -d markdownlint-cli2')
99
+ fail('markdownlint-cli2 відсутній — bun add -d markdownlint-cli2 (n-text.mdc)')
94
100
  }
95
101
 
96
102
  const lintText = pkg.scripts?.['lint-text']
@@ -99,7 +105,7 @@ export async function check() {
99
105
  if (
100
106
  typeof lintText === 'string' &&
101
107
  lintText.includes('cspell') &&
102
- lintText.includes('markdownlint-cli2') &&
108
+ lintText.includes('bunx markdownlint-cli2') &&
103
109
  lintText.includes('**/*.mdc') &&
104
110
  v8rCalls >= 4 &&
105
111
  eq98Hints >= 4 &&
@@ -1,10 +1,104 @@
1
+ /**
2
+ * Знаходить пакети з `vue` у dependencies і перевіряє їх за правилом vue.mdc.
3
+ *
4
+ * Версії Vite та плагінів, vue-macros, auto-import, layouts, вміст `vite.config`;
5
+ * у репозиторії — рекомендацію розширення Vue.volar.
6
+ */
1
7
  import { existsSync } from 'node:fs'
2
8
  import { readFile } from 'node:fs/promises'
9
+ import { join } from 'node:path'
3
10
 
4
11
  import { pass } from './utils/pass.mjs'
12
+ import { getMonorepoPackageRootDirs } from './utils/workspaces.mjs'
13
+
14
+ /**
15
+ * Формує зрозумілий для людини підпис пакета для повідомлень перевірки.
16
+ * @param {string} rootDir відносний шлях (`'.'` або `site` тощо)
17
+ * @returns {string} підпис для логів перевірки
18
+ */
19
+ function packageLabel(rootDir) {
20
+ return rootDir === '.' ? 'корінь' : rootDir
21
+ }
22
+
23
+ /**
24
+ * Перевіряє залежності та vite.config одного Vue-пакета.
25
+ * @param {string} rootDir відносний шлях до пакета
26
+ * @param {(msg: string) => void} fail функція зворотного виклику для реєстрації помилки перевірки
27
+ * @returns {Promise<void>} завершується після перевірок залежностей і Vite
28
+ */
29
+ async function checkVuePackage(rootDir, fail) {
30
+ const label = packageLabel(rootDir)
31
+ const prefix = `[${label}] `
32
+
33
+ const pkgPath = join(rootDir, 'package.json')
34
+ const pkg = JSON.parse(await readFile(pkgPath, 'utf8'))
35
+ const deps = pkg.dependencies || {}
36
+ const devDeps = pkg.devDependencies || {}
37
+ const allDeps = { ...deps, ...devDeps }
38
+
39
+ if (deps.vue) {
40
+ pass(`${prefix}vue в dependencies: ${deps.vue}`)
41
+ } else {
42
+ fail(`${prefix}vue відсутній в dependencies`)
43
+ }
44
+
45
+ if (devDeps.vite) {
46
+ const match = devDeps.vite.match(/(\d+)/)
47
+ if (match && Number(match[1]) >= 8) {
48
+ pass(`${prefix}vite >= 8: ${devDeps.vite}`)
49
+ } else {
50
+ fail(`${prefix}vite має бути >= 8, знайдено: ${devDeps.vite}`)
51
+ }
52
+ } else {
53
+ fail(`${prefix}vite відсутній в devDependencies`)
54
+ }
55
+
56
+ if (devDeps['@vitejs/plugin-vue']) {
57
+ pass(`${prefix}@vitejs/plugin-vue: ${devDeps['@vitejs/plugin-vue']}`)
58
+ } else {
59
+ fail(`${prefix}@vitejs/plugin-vue відсутній в devDependencies`)
60
+ }
61
+
62
+ if (allDeps['vue-macros']) {
63
+ pass(`${prefix}vue-macros: ${allDeps['vue-macros']}`)
64
+ } else {
65
+ fail(`${prefix}vue-macros відсутній — bun add -d vue-macros`)
66
+ }
67
+
68
+ if (allDeps['unplugin-auto-import']) {
69
+ pass(`${prefix}unplugin-auto-import присутній`)
70
+ } else {
71
+ fail(`${prefix}unplugin-auto-import відсутній — bun add -d unplugin-auto-import`)
72
+ }
73
+
74
+ if (allDeps['vite-plugin-vue-layouts-next']) {
75
+ pass(`${prefix}vite-plugin-vue-layouts-next присутній`)
76
+ } else {
77
+ fail(`${prefix}vite-plugin-vue-layouts-next відсутній — bun add -d vite-plugin-vue-layouts-next`)
78
+ }
79
+
80
+ const configFiles = ['vite.config.js', 'vite.config.ts', 'vite.config.mjs']
81
+ const viteConfig = configFiles.find(f => existsSync(join(rootDir, f)))
82
+ if (viteConfig) {
83
+ const relConfig = join(rootDir, viteConfig)
84
+ const content = await readFile(relConfig, 'utf8')
85
+ if (content.includes('VueMacros')) {
86
+ pass(`${prefix}${viteConfig} використовує VueMacros`)
87
+ } else {
88
+ fail(`${prefix}${viteConfig} не містить VueMacros`)
89
+ }
90
+ if (content.includes('AutoImport')) {
91
+ pass(`${prefix}${viteConfig} використовує AutoImport`)
92
+ } else {
93
+ fail(`${prefix}${viteConfig} не містить AutoImport`)
94
+ }
95
+ } else {
96
+ fail(`${prefix}немає vite.config.js|ts|mjs у каталозі пакета`)
97
+ }
98
+ }
5
99
 
6
100
  /**
7
- * Перевіряє відповідність проєкту правилам vue.mdc
101
+ * Перевіряє відповідність проєкту правилам vue.mdc (корінь і всі workspace-пакети з `vue` у dependencies).
8
102
  * @returns {Promise<number>} 0 — все OK, 1 — є проблеми
9
103
  */
10
104
  export async function check() {
@@ -25,70 +119,24 @@ export async function check() {
25
119
  fail('.vscode/extensions.json не існує')
26
120
  }
27
121
 
28
- if (existsSync('package.json')) {
29
- const pkg = JSON.parse(await readFile('package.json', 'utf8'))
30
- const deps = pkg.dependencies || {}
31
- const devDeps = pkg.devDependencies || {}
32
- const allDeps = { ...deps, ...devDeps }
33
-
34
- if (deps.vue) {
35
- pass(`vue в dependencies: ${deps.vue}`)
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')
122
+ const roots = await getMonorepoPackageRootDirs()
123
+ /** @type {string[]} */
124
+ const vueRoots = []
125
+ for (const r of roots) {
126
+ const p = join(r, 'package.json')
127
+ if (existsSync(p)) {
128
+ const pkg = JSON.parse(await readFile(p, 'utf8'))
129
+ if (pkg.dependencies?.vue) vueRoots.push(r)
67
130
  }
131
+ }
68
132
 
69
- if (allDeps['vite-plugin-vue-layouts-next']) {
70
- pass('vite-plugin-vue-layouts-next присутній')
71
- } else {
72
- fail('vite-plugin-vue-layouts-next відсутній — bun add -d vite-plugin-vue-layouts-next')
73
- }
133
+ if (vueRoots.length === 0) {
134
+ fail('vue не знайдено в dependencies жодного пакета (корінь репо та каталоги з кореневого workspaces)')
135
+ return exitCode
74
136
  }
75
137
 
76
- const configFiles = ['vite.config.js', 'vite.config.ts', 'vite.config.mjs']
77
- const viteConfig = configFiles.find(f => existsSync(f))
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 не існує')
138
+ for (const r of vueRoots) {
139
+ await checkVuePackage(r, fail)
92
140
  }
93
141
 
94
142
  return exitCode
@@ -1,5 +1,7 @@
1
1
  /**
2
- * Логує успішний результат перевірки (рядок з галочкою)
2
+ * Допоміжна функція для скриптів перевірки.
3
+ *
4
+ * Друкує в консоль рядок успіху з галочкою (✅).
3
5
  * @param {string} msg текст повідомлення
4
6
  */
5
7
  export function pass(msg) {
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Допоміжний модуль для скриптів перевірки монорепо.
3
+ *
4
+ * Зчитує кореневий `package.json` і повертає список каталогів-пакетів (корінь `.` плюс шляхи
5
+ * з `workspaces`, з урахуванням glob).
6
+ */
7
+ import { existsSync } from 'node:fs'
8
+ import { glob, readFile } from 'node:fs/promises'
9
+ import { dirname, join, relative } from 'node:path'
10
+
11
+ /**
12
+ * Нормалізує поле `workspaces` з package.json до масиву шляхів / glob-патернів.
13
+ * @param {unknown} workspaces значення `workspaces` з кореневого package.json
14
+ * @returns {string[]} масив патернів workspaces
15
+ */
16
+ export function normalizeWorkspacePatterns(workspaces) {
17
+ if (!workspaces) return []
18
+ if (Array.isArray(workspaces)) return workspaces
19
+ if (typeof workspaces === 'object' && workspaces !== null && Array.isArray(workspaces.packages)) {
20
+ return workspaces.packages
21
+ }
22
+ return []
23
+ }
24
+
25
+ /**
26
+ * Повертає каталоги з `package.json`: корінь репозиторію та всі пакети з `workspaces`.
27
+ * @param {string} repoRoot зазвичай `process.cwd()`
28
+ * @returns {Promise<string[]>} відносні шляхи до коренів пакетів; `'.'` першим, без дублікатів
29
+ */
30
+ export async function getMonorepoPackageRootDirs(repoRoot = '.') {
31
+ const roots = new Set(['.'])
32
+ const rootPkgPath = join(repoRoot, 'package.json')
33
+ if (!existsSync(rootPkgPath)) {
34
+ return ['.']
35
+ }
36
+ const pkg = JSON.parse(await readFile(rootPkgPath, 'utf8'))
37
+ for (const raw of normalizeWorkspacePatterns(pkg.workspaces)) {
38
+ const w = raw.replaceAll('\\', '/').replace(/\/+$/, '') || '.'
39
+ if (w.includes('*')) {
40
+ const globPat = `${w}/package.json`
41
+ for await (const f of glob(globPat, { cwd: repoRoot })) {
42
+ const abs = join(repoRoot, f)
43
+ const rel = relative(repoRoot, dirname(abs))
44
+ roots.add(rel === '' ? '.' : rel)
45
+ }
46
+ } else {
47
+ const pkgJson = join(repoRoot, w, 'package.json')
48
+ if (existsSync(pkgJson)) roots.add(w)
49
+ }
50
+ }
51
+ const list = [...roots]
52
+ list.sort((a, b) => {
53
+ if (a === '.') return -1
54
+ if (b === '.') return 1
55
+ return a.localeCompare(b)
56
+ })
57
+ return list
58
+ }