@nitra/cursor 1.4.1 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/bin/n-cursor.js +34 -19
- package/mdc/bun.mdc +8 -2
- package/mdc/js-pino.mdc +1 -1
- package/mdc/style-lint.mdc +1 -1
- package/mdc/text.mdc +218 -0
- package/mdc/vue.mdc +0 -2
- package/package.json +1 -1
- package/scripts/check-bun.mjs +22 -7
- package/scripts/check-ga.mjs +22 -11
- package/scripts/check-js-format.mjs +26 -12
- package/scripts/check-js-lint.mjs +29 -14
- package/scripts/check-js-pino.mjs +2 -1
- package/scripts/check-nginx-default-tpl.mjs +27 -16
- package/scripts/check-npm-module.mjs +22 -9
- package/scripts/check-style-lint.mjs +37 -18
- package/scripts/check-text.mjs +139 -0
- package/scripts/check-vue.mjs +42 -19
- package/scripts/utils/pass.mjs +7 -0
- package/skills/{n-fix-cursor → n-fix}/SKILL.md +7 -2
- package/skills/n-publish-telegram/SKILL.md +8 -7
- package/mdc/spell.mdc +0 -155
- package/scripts/check-spell.mjs +0 -57
- package/skills/n-lint/SKILL.md +0 -26
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
```json
|
|
16
16
|
{
|
|
17
|
-
"rules": ["js-format", "npm-module", "
|
|
17
|
+
"rules": ["js-format", "npm-module", "text"]
|
|
18
18
|
}
|
|
19
19
|
```
|
|
20
20
|
|
|
@@ -24,14 +24,14 @@
|
|
|
24
24
|
| ------------ | ------------------------------------------------- |
|
|
25
25
|
| `js-format` | Правила форматування JavaScript ecosystem (oxfmt) |
|
|
26
26
|
| `npm-module` | Структура репозиторію для npm-модуля (bun mono) |
|
|
27
|
-
| `
|
|
27
|
+
| `text` | Текстові файли: cspell, CI |
|
|
28
28
|
|
|
29
29
|
Щоб завантажити правила конкретної версії пакету, додайте поле `version`:
|
|
30
30
|
|
|
31
31
|
```json
|
|
32
32
|
{
|
|
33
33
|
"version": "2.5.0",
|
|
34
|
-
"rules": ["js-format", "
|
|
34
|
+
"rules": ["js-format", "text"]
|
|
35
35
|
}
|
|
36
36
|
```
|
|
37
37
|
|
|
@@ -56,7 +56,7 @@ CLI автоматично:
|
|
|
56
56
|
📋 Правил до завантаження: 3
|
|
57
57
|
⬇ js-format → .cursor/rules/n-js-format.mdc ... ✅
|
|
58
58
|
⬇ npm-module → .cursor/rules/n-npm-module.mdc ... ✅
|
|
59
|
-
⬇
|
|
59
|
+
⬇ text → .cursor/rules/n-text.mdc ... ✅
|
|
60
60
|
📝 Оновлено AGENTS.md з AGENTS.template.md
|
|
61
61
|
|
|
62
62
|
✨ Готово: 3 завантажено, 0 з помилками
|
|
@@ -70,7 +70,7 @@ npm/
|
|
|
70
70
|
├── mdc/ # cursor-правила
|
|
71
71
|
│ ├── js-format.mdc
|
|
72
72
|
│ ├── npm-module.mdc
|
|
73
|
-
│ └──
|
|
73
|
+
│ └── text.mdc
|
|
74
74
|
└── bin/
|
|
75
75
|
└── n-cursor.js # CLI-скрипт
|
|
76
76
|
```
|
|
@@ -96,7 +96,7 @@ npm/
|
|
|
96
96
|
{{/services}}
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
-
Під час запуску CLI тіло між `{{#services}}` і `{{/services}}` повторюється для кожного `*.mdc` у `.cursor/rules/`; у `{{name}}` підставляється вже готовий markdown-рядок (наприклад `- .cursor/rules/n-
|
|
99
|
+
Під час запуску CLI тіло між `{{#services}}` і `{{/services}}` повторюється для кожного `*.mdc` у `.cursor/rules/`; у `{{name}}` підставляється вже готовий markdown-рядок (наприклад `- .cursor/rules/n-text.mdc`).
|
|
100
100
|
|
|
101
101
|
3. Після змін у шаблоні перевірте локально: у тестовому репозиторії з `.n-cursor.json` виконайте `npx`/`bunx` на зібраному пакеті або `node npm/bin/n-cursor.js` з кореня того репозиторію і переконайтеся, що **`AGENTS.md`** виглядає як очікується.
|
|
102
102
|
|
package/bin/n-cursor.js
CHANGED
|
@@ -107,20 +107,19 @@ async function migrateLegacyManagedRuleFilenames(rulesDir) {
|
|
|
107
107
|
}
|
|
108
108
|
const names = await readdir(rulesDir)
|
|
109
109
|
for (const name of names) {
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
110
|
+
if (name.endsWith('.mdc') && name.startsWith('nitra-')) {
|
|
111
|
+
const rest = name.slice('nitra-'.length)
|
|
112
|
+
const newName = `${RULE_PREFIX}${rest}`
|
|
113
|
+
const from = join(rulesDir, name)
|
|
114
|
+
const to = join(rulesDir, newName)
|
|
115
|
+
if (existsSync(to)) {
|
|
116
|
+
await unlink(from)
|
|
117
|
+
console.log(`📝 Видалено застарілий ${RULES_DIR}/${name} (вже є ${newName})\n`)
|
|
118
|
+
} else {
|
|
119
|
+
await rename(from, to)
|
|
120
|
+
console.log(`📝 Перейменовано ${RULES_DIR}/${name} → ${RULES_DIR}/${newName}\n`)
|
|
121
|
+
}
|
|
121
122
|
}
|
|
122
|
-
await rename(from, to)
|
|
123
|
-
console.log(`📝 Перейменовано ${RULES_DIR}/${name} → ${RULES_DIR}/${newName}\n`)
|
|
124
123
|
}
|
|
125
124
|
}
|
|
126
125
|
|
|
@@ -168,7 +167,7 @@ async function readConfig() {
|
|
|
168
167
|
throw new Error(`Невірний JSON у файлі ${CONFIG_FILE}`)
|
|
169
168
|
}
|
|
170
169
|
if (!Array.isArray(config.rules) || config.rules.length === 0) {
|
|
171
|
-
throw new Error(`У ${CONFIG_FILE} має бути
|
|
170
|
+
throw new Error(`У ${CONFIG_FILE} має бути непорожній масив "rules"`)
|
|
172
171
|
}
|
|
173
172
|
if (!Array.isArray(config.skills)) {
|
|
174
173
|
if ('skills' in config) {
|
|
@@ -204,7 +203,7 @@ function normalizeRuleName(ruleName) {
|
|
|
204
203
|
}
|
|
205
204
|
|
|
206
205
|
/**
|
|
207
|
-
* Нормалізує id skill з конфігу до форми без префікса n- (як «fix
|
|
206
|
+
* Нормалізує id skill з конфігу до форми без префікса n- (як «fix»)
|
|
208
207
|
* @param {string} skillName елемент масиву skills або ім'я каталогу
|
|
209
208
|
* @returns {string} id без префікса n-
|
|
210
209
|
*/
|
|
@@ -216,13 +215,28 @@ function normalizeSkillId(skillName) {
|
|
|
216
215
|
return s
|
|
217
216
|
}
|
|
218
217
|
|
|
218
|
+
/** Legacy id у `.n-cursor.json` → поточний bundled id (каталог `n-<id>` у пакеті) */
|
|
219
|
+
const LEGACY_SKILL_ID_MAP = {
|
|
220
|
+
'fix-cursor': 'fix'
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Поточний id skill для шляхів у пакеті та `.cursor/skills`
|
|
225
|
+
* @param {string} skillName елемент масиву skills або ім'я каталогу
|
|
226
|
+
* @returns {string} canonical id без префікса n-
|
|
227
|
+
*/
|
|
228
|
+
function canonicalSkillId(skillName) {
|
|
229
|
+
const id = normalizeSkillId(skillName)
|
|
230
|
+
return LEGACY_SKILL_ID_MAP[id] ?? id
|
|
231
|
+
}
|
|
232
|
+
|
|
219
233
|
/**
|
|
220
234
|
* Ім'я керованого каталогу skill у .cursor/skills (префікс n-)
|
|
221
235
|
* @param {string} skillId id без префікса
|
|
222
|
-
* @returns {string} наприклад n-fix
|
|
236
|
+
* @returns {string} наприклад n-fix
|
|
223
237
|
*/
|
|
224
238
|
function managedSkillDirName(skillId) {
|
|
225
|
-
return `${RULE_PREFIX}${
|
|
239
|
+
return `${RULE_PREFIX}${canonicalSkillId(skillId)}`
|
|
226
240
|
}
|
|
227
241
|
|
|
228
242
|
/**
|
|
@@ -429,12 +443,13 @@ async function syncSkills(configSkills) {
|
|
|
429
443
|
let fail = 0
|
|
430
444
|
|
|
431
445
|
for (const skillId of configSkills) {
|
|
446
|
+
const id = canonicalSkillId(skillId)
|
|
432
447
|
const dirName = managedSkillDirName(skillId)
|
|
433
448
|
const srcDir = join(BUNDLED_SKILLS_DIR, dirName)
|
|
434
449
|
const destDir = join(skillsRoot, dirName)
|
|
435
450
|
|
|
436
451
|
if (existsSync(srcDir)) {
|
|
437
|
-
process.stdout.write(` ⬇ ${
|
|
452
|
+
process.stdout.write(` ⬇ ${id} → ${SKILLS_DIR}/${dirName} ... `)
|
|
438
453
|
try {
|
|
439
454
|
await mkdir(destDir, { recursive: true })
|
|
440
455
|
const files = await readdir(srcDir)
|
|
@@ -450,7 +465,7 @@ async function syncSkills(configSkills) {
|
|
|
450
465
|
fail++
|
|
451
466
|
}
|
|
452
467
|
} else {
|
|
453
|
-
process.stdout.write(` ⬇ ${
|
|
468
|
+
process.stdout.write(` ⬇ ${id} → ${SKILLS_DIR}/${dirName} ... `)
|
|
454
469
|
console.log(`❌`)
|
|
455
470
|
console.error(` Немає каталогу в пакеті: ${dirName}`)
|
|
456
471
|
fail++
|
package/mdc/bun.mdc
CHANGED
|
@@ -12,9 +12,11 @@ version: '1.1'
|
|
|
12
12
|
- Якщо новий виклик CLI додається з нуля і в цьому репозиторії прийнято лише Bun — можна `bunx <tool>`.
|
|
13
13
|
|
|
14
14
|
Заборонено використовувати як менеджер пакетів / lockfile:
|
|
15
|
+
|
|
15
16
|
- `npm install`, `yarn`, `pnpm` (і відповідні lockfile, крім `bun.lock`)
|
|
16
17
|
|
|
17
18
|
Дозволені команди:
|
|
19
|
+
|
|
18
20
|
- `bun i`
|
|
19
21
|
- `bun run <script>`
|
|
20
22
|
- `bun add <pkg>`
|
|
@@ -23,7 +25,6 @@ version: '1.1'
|
|
|
23
25
|
- `bunx <tool>`
|
|
24
26
|
- `npx <tool>`
|
|
25
27
|
|
|
26
|
-
|
|
27
28
|
- Для встановлення залежностей використовуй `bun i`.
|
|
28
29
|
- Для запуску скриптів використовуй `bun run <script>`.
|
|
29
30
|
- Для додавання залежностей:
|
|
@@ -32,6 +33,7 @@ version: '1.1'
|
|
|
32
33
|
- Для одноразових CLI-команд використовуй `bunx <tool>`.
|
|
33
34
|
|
|
34
35
|
Заборонено використовувати:
|
|
36
|
+
|
|
35
37
|
- `npm`
|
|
36
38
|
- `yarn`
|
|
37
39
|
- `pnpm`
|
|
@@ -41,20 +43,24 @@ Lockfile у репозиторії: `bun.lock`.
|
|
|
41
43
|
Видалити якщо вони є. Видалити .yarn та .yarnrc.yml якщо вони є.
|
|
42
44
|
|
|
43
45
|
Для Bun monorepo:
|
|
46
|
+
|
|
44
47
|
- Встановлювати залежності у відповідному пакеті, а не в корені без потреби.
|
|
45
48
|
- Якщо залежність потрібна лише одному пакету, додавати її в директорії цього пакета.
|
|
46
49
|
- У CI та локально запускати скрипти через `bun run`.
|
|
47
50
|
|
|
48
|
-
Якщо в package.json є поля `packageManager`, то прибрати їх, також прибрати всі директорії та
|
|
51
|
+
Якщо в package.json є поля `packageManager`, то прибрати їх, також прибрати всі директорії та файли для yarn
|
|
49
52
|
|
|
50
53
|
Якщо в проекті використовується npx, то не заміняти його на bunx, а використовувати npx.
|
|
51
54
|
Коли зміна відбувається в Dockerfile, то використовувати
|
|
55
|
+
|
|
52
56
|
```dockerfile
|
|
53
57
|
FROM oven/bun:alpine AS build-env
|
|
54
58
|
```
|
|
59
|
+
|
|
55
60
|
замість образу node
|
|
56
61
|
|
|
57
62
|
В Github actions bun повинен налаштований так:
|
|
63
|
+
|
|
58
64
|
```yaml
|
|
59
65
|
- uses: oven-sh/setup-bun@v2
|
|
60
66
|
|
package/mdc/js-pino.mdc
CHANGED
package/mdc/style-lint.mdc
CHANGED
|
@@ -8,7 +8,7 @@ version: '1.1'
|
|
|
8
8
|
|
|
9
9
|
- **Джерело правил:** перед тим як писати або суттєво змінювати **`.css`**, **`.scss`** або стилі в **`.vue`**, переглянь у корені проєкту (і в релевантних пакетах монорепо, якщо є) поле **`stylelint`** у **`package.json`** (зокрема `extends`), наявні **`.stylelintrc.*`**, **`stylelint.config.*`** та **`.stylelintignore`**. Не покладайся на «типові» правила stylelint з пам’яті — дотримуйся **проєктного** **`@nitra/stylelint-config`** і будь-яких локальних доповнень у репозиторії.
|
|
10
10
|
- **Форматування** узгоджуй з **`n-js-format.mdc`** (oxfmt / `.oxfmtrc.json` для css, scss тощо), щоб форматер і stylelint не суперечили один одному.
|
|
11
|
-
- **Після змін:** запускай **`bun run lint-style`** (або `bunx stylelint` з тими ж glob-ами та прапорцями, що в скрипті та CI) і виправляй усе, що лишилось після auto-fix.
|
|
11
|
+
- **Після змін:** запускай **`bun run lint-style`** (або `bunx stylelint` з тими ж glob-ами та прапорцями, що в скрипті та CI) і виправляй усе, що лишилось після auto-fix. За потреби пройдись повним набором `lint-*` з `package.json` (див. навичку **`n-fix`**).
|
|
12
12
|
- **Не розширюй винятки:** не додавай зайві **`stylelint-disable`** / вузькі придушення правил без потреби; краще змінити стилі під правила проєкту.
|
|
13
13
|
|
|
14
14
|
В файлі .vscode/extensions.json є налаштування для офіційного плагіну:
|
package/mdc/text.mdc
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Обробка та перевірка текстових файлів (cspell, markdownlint-cli2, v8r, CI)
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
version: '1.10'
|
|
5
|
+
---
|
|
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):
|
|
12
|
+
|
|
13
|
+
```json title=".vscode/extensions.json"
|
|
14
|
+
{
|
|
15
|
+
"recommendations": [
|
|
16
|
+
"dbaeumer.vscode-eslint",
|
|
17
|
+
"github.vscode-github-actions",
|
|
18
|
+
"oxc.oxc-vscode",
|
|
19
|
+
"DavidAnson.vscode-markdownlint"
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Скрипти та залежності в package.json
|
|
25
|
+
|
|
26
|
+
У кореневому `package.json` один скрипт **lint-text** має послідовно викликати **cspell**, **markdownlint-cli2** і **v8r** (і решту блоків з розділів нижче в одному файлі):
|
|
27
|
+
|
|
28
|
+
```json title="package.json"
|
|
29
|
+
"scripts": {
|
|
30
|
+
"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
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"markdownlint-cli2": "^0.22.0"
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
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`**. Якщо для файлу немає схеми в каталозі, запуск завершиться з помилкою — додай такі шляхи в **`.v8rignore`**.
|
|
46
|
+
|
|
47
|
+
## Конфігурація markdownlint-cli2
|
|
48
|
+
|
|
49
|
+
У корені проєкту має бути **`.markdownlint-cli2.jsonc`** (документація: [DavidAnson/markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2)):
|
|
50
|
+
|
|
51
|
+
```json title=".markdownlint-cli2.jsonc"
|
|
52
|
+
{
|
|
53
|
+
"gitignore": true,
|
|
54
|
+
"config": {
|
|
55
|
+
"default": true,
|
|
56
|
+
"MD013": false,
|
|
57
|
+
"MD029": false,
|
|
58
|
+
"MD040": false,
|
|
59
|
+
"MD041": false
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`gitignore: true` змушує markdownlint-cli2 пропускати шляхи з `.gitignore` (у тому числі `node_modules`, якщо воно там є). Додатковий glob `"#node_modules"` у скрипті не потрібен. За потреби додай власні шляхи через `ignores` у цьому ж файлі.
|
|
65
|
+
|
|
66
|
+
За потреби увімкни окремі правила (наприклад `MD013` з `line_length: 120`), щоб узгодити довжину рядків з `.oxfmtrc.json`.
|
|
67
|
+
|
|
68
|
+
`MD041` вимкнено навмисно: файли `.mdc` (Cursor rules) мають YAML frontmatter, після якого перший абзац часто не є заголовком H1.
|
|
69
|
+
|
|
70
|
+
## Cspell
|
|
71
|
+
|
|
72
|
+
У корені проєкту має бути `.cspell.json` і залежності для cspell у кореневому `package.json` (зазвичай `devDependencies`).
|
|
73
|
+
|
|
74
|
+
Додай workflow `.github/workflows/lint-text.yml`:
|
|
75
|
+
|
|
76
|
+
```yaml
|
|
77
|
+
name: Lint Text
|
|
78
|
+
|
|
79
|
+
on:
|
|
80
|
+
push:
|
|
81
|
+
branches:
|
|
82
|
+
- dev
|
|
83
|
+
paths:
|
|
84
|
+
- '.cspell.json'
|
|
85
|
+
- '.gitignore'
|
|
86
|
+
- '.markdownlint-cli2.jsonc'
|
|
87
|
+
- '.v8rignore'
|
|
88
|
+
- '**/*.js'
|
|
89
|
+
- '**/*.ts'
|
|
90
|
+
- '**/*.vue'
|
|
91
|
+
- '**/*.html'
|
|
92
|
+
- '**/*.css'
|
|
93
|
+
- '**/*.scss'
|
|
94
|
+
- '**/*.less'
|
|
95
|
+
- '**/*.json'
|
|
96
|
+
- '**/*.jsonc'
|
|
97
|
+
- '**/*.yaml'
|
|
98
|
+
- '**/*.yml'
|
|
99
|
+
- '**/*.toml'
|
|
100
|
+
- '**/*.xml'
|
|
101
|
+
- '**/*.md'
|
|
102
|
+
- '**/*.mdc'
|
|
103
|
+
- '**/*.mdс'
|
|
104
|
+
- '**/*.txt'
|
|
105
|
+
- '**/*.go'
|
|
106
|
+
- '**/*.py'
|
|
107
|
+
- '**/*.php'
|
|
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
|
+
text:
|
|
119
|
+
runs-on: ubuntu-latest
|
|
120
|
+
steps:
|
|
121
|
+
- uses: actions/checkout@v4
|
|
122
|
+
|
|
123
|
+
- uses: oven-sh/setup-bun@v2
|
|
124
|
+
|
|
125
|
+
- name: Cache Bun dependencies
|
|
126
|
+
uses: actions/cache@v4
|
|
127
|
+
with:
|
|
128
|
+
path: |
|
|
129
|
+
~/.bun/install/cache
|
|
130
|
+
node_modules
|
|
131
|
+
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
|
|
132
|
+
restore-keys: |
|
|
133
|
+
${{ runner.os }}-bun-
|
|
134
|
+
|
|
135
|
+
- name: Install dependencies
|
|
136
|
+
run: bun install --frozen-lockfile
|
|
137
|
+
|
|
138
|
+
- name: Lint text
|
|
139
|
+
run: bun run lint-text
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Без дублювання CI:** якщо в `.github/workflows` уже є workflow з тими самими кроками `cspell` або `markdownlint-cli2`, видали зайвий — перевірки тексту та Markdown мають виконуватися в одному місці.
|
|
143
|
+
|
|
144
|
+
## Базовий варіант cspell (без окремого словника української)
|
|
145
|
+
|
|
146
|
+
Якщо текст переважно англійською та достатньо корпоративного словника (@nitra/cspell-dict; у полі `language` cspell лишається тег `nitra`):
|
|
147
|
+
|
|
148
|
+
```json title=".cspell.json"
|
|
149
|
+
{
|
|
150
|
+
"version": "0.2",
|
|
151
|
+
"language": "nitra",
|
|
152
|
+
"ignorePaths": ["**/node_modules/**", "**/vscode-extension/**", "**/.git/**", ".vscode", "report"],
|
|
153
|
+
"import": ["@nitra/cspell-dict/cspell-ext.json"],
|
|
154
|
+
"words": []
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
```json title="package.json"
|
|
159
|
+
{
|
|
160
|
+
"scripts": {
|
|
161
|
+
"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 ])"
|
|
162
|
+
},
|
|
163
|
+
"devDependencies": {
|
|
164
|
+
"@nitra/cspell-dict": "^1.0.185",
|
|
165
|
+
"markdownlint-cli2": "^0.22.0"
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Проєкт з українською мовою
|
|
171
|
+
|
|
172
|
+
Якщо в репозиторії є українська документація, коментарі або рядки в коді — потрібен **окремий словник** `@cspell/dict-uk-ua`, інакше cspell не перевірятиме український правопис коректно.
|
|
173
|
+
|
|
174
|
+
**1. Залежності** — додай пакет словника поруч із `@nitra/cspell-dict`:
|
|
175
|
+
|
|
176
|
+
```json title="package.json"
|
|
177
|
+
{
|
|
178
|
+
"scripts": {
|
|
179
|
+
"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 ])"
|
|
180
|
+
},
|
|
181
|
+
"devDependencies": {
|
|
182
|
+
"@nitra/cspell-dict": "^1.0.185",
|
|
183
|
+
"@cspell/dict-uk-ua": "^4.0.6",
|
|
184
|
+
"markdownlint-cli2": "^0.22.0"
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Встановлення: `bun add -d @cspell/dict-uk-ua` (або `npm i -D @cspell/dict-uk-ua`).
|
|
190
|
+
|
|
191
|
+
**2. `.cspell.json`** — у полі `language` має бути `uk` (разом з іншими мовами через кому), у `import` — підключення розширення українського словника:
|
|
192
|
+
|
|
193
|
+
```json title=".cspell.json"
|
|
194
|
+
{
|
|
195
|
+
"version": "0.2",
|
|
196
|
+
"language": "en,uk,nitra",
|
|
197
|
+
"ignorePaths": ["**/node_modules/**", "**/vscode-extension/**", "**/.git/**", ".vscode", "report"],
|
|
198
|
+
"import": [
|
|
199
|
+
"@nitra/cspell-dict/cspell-ext.json",
|
|
200
|
+
"@cspell/dict-uk-ua/cspell-ext.json"
|
|
201
|
+
],
|
|
202
|
+
"words": []
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Підлаштуй `language` під проєкт (наприклад додай `ru-ru`, якщо потрібна перевірка російською). Порядок у `import` може впливати на пріоритет словників — тримай корпоративний `@nitra/cspell-dict` там, де зручно для ваших правил.
|
|
207
|
+
|
|
208
|
+
## Локальні виключення
|
|
209
|
+
|
|
210
|
+
У секції `words` у `.cspell.json` додають власні терміни, імена та скорочення, яких немає в словниках.
|
|
211
|
+
|
|
212
|
+
## Інші мови
|
|
213
|
+
|
|
214
|
+
Для іншої мови встанови відповідний пакет `@cspell/dict-*`, додай його `cspell-ext.json` у `import` і код мови в `language`. Огляд словників: [streetsidesoftware/cspell-dicts](https://github.com/streetsidesoftware/cspell-dicts).
|
|
215
|
+
|
|
216
|
+
## Перевірка
|
|
217
|
+
|
|
218
|
+
`npx @nitra/cursor check text`
|
package/mdc/vue.mdc
CHANGED
package/package.json
CHANGED
package/scripts/check-bun.mjs
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
2
|
import { readFile } from 'node:fs/promises'
|
|
3
3
|
|
|
4
|
+
import { pass } from './utils/pass.mjs'
|
|
5
|
+
|
|
4
6
|
/**
|
|
5
7
|
* Перевіряє відповідність проєкту правилам bun.mdc
|
|
6
8
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
7
9
|
*/
|
|
8
10
|
export async function check() {
|
|
9
11
|
let exitCode = 0
|
|
10
|
-
const pass = msg => console.log(` ✅ ${msg}`)
|
|
11
12
|
const fail = msg => {
|
|
12
13
|
console.log(` ❌ ${msg}`)
|
|
13
14
|
exitCode = 1
|
|
@@ -15,17 +16,31 @@ export async function check() {
|
|
|
15
16
|
|
|
16
17
|
const forbidden = ['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', '.yarnrc.yml']
|
|
17
18
|
for (const f of forbidden) {
|
|
18
|
-
existsSync(f)
|
|
19
|
+
if (existsSync(f)) {
|
|
20
|
+
fail(`Знайдено заборонений файл: ${f} — видали його`)
|
|
21
|
+
} else {
|
|
22
|
+
pass(`Немає ${f}`)
|
|
23
|
+
}
|
|
19
24
|
}
|
|
20
25
|
|
|
21
|
-
existsSync('.yarn')
|
|
22
|
-
|
|
26
|
+
if (existsSync('.yarn')) {
|
|
27
|
+
fail('Знайдено директорію .yarn — видали її')
|
|
28
|
+
} else {
|
|
29
|
+
pass('Немає .yarn/')
|
|
30
|
+
}
|
|
31
|
+
if (existsSync('bun.lock')) {
|
|
32
|
+
pass('bun.lock є')
|
|
33
|
+
} else {
|
|
34
|
+
fail('Відсутній bun.lock — запусти bun i')
|
|
35
|
+
}
|
|
23
36
|
|
|
24
37
|
if (existsSync('package.json')) {
|
|
25
38
|
const pkg = JSON.parse(await readFile('package.json', 'utf8'))
|
|
26
|
-
pkg.packageManager
|
|
27
|
-
|
|
28
|
-
|
|
39
|
+
if (pkg.packageManager) {
|
|
40
|
+
fail(`package.json містить поле packageManager: "${pkg.packageManager}" — видали його`)
|
|
41
|
+
} else {
|
|
42
|
+
pass('package.json не містить packageManager')
|
|
43
|
+
}
|
|
29
44
|
}
|
|
30
45
|
|
|
31
46
|
return exitCode
|
package/scripts/check-ga.mjs
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
2
|
import { readdir, readFile } from 'node:fs/promises'
|
|
3
3
|
|
|
4
|
+
import { pass } from './utils/pass.mjs'
|
|
5
|
+
|
|
4
6
|
/**
|
|
5
7
|
* Перевіряє відповідність проєкту правилам ga.mdc
|
|
6
8
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
7
9
|
*/
|
|
8
10
|
export async function check() {
|
|
9
11
|
let exitCode = 0
|
|
10
|
-
const pass = msg => console.log(` ✅ ${msg}`)
|
|
11
12
|
const fail = msg => {
|
|
12
13
|
console.log(` ❌ ${msg}`)
|
|
13
14
|
exitCode = 1
|
|
@@ -30,28 +31,38 @@ export async function check() {
|
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
for (const f of ['clean-ga-workflows.yml', 'clean-merged-branch.yml']) {
|
|
33
|
-
files.includes(f)
|
|
34
|
+
if (files.includes(f)) {
|
|
35
|
+
pass(`${f} існує`)
|
|
36
|
+
} else {
|
|
37
|
+
fail(`Відсутній ${wfDir}/${f}`)
|
|
38
|
+
}
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
if (files.includes('apply-k8s.yml')) {
|
|
37
42
|
const content = await readFile(`${wfDir}/apply-k8s.yml`, 'utf8')
|
|
38
|
-
content.includes('**/k8s/*.yaml')
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
if (content.includes('**/k8s/*.yaml')) {
|
|
44
|
+
pass('apply-k8s.yml має правильний paths trigger')
|
|
45
|
+
} else {
|
|
46
|
+
fail('apply-k8s.yml не містить paths: **/k8s/*.yaml')
|
|
47
|
+
}
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
if (files.includes('apply-nats-consumer.yml')) {
|
|
44
51
|
const content = await readFile(`${wfDir}/apply-nats-consumer.yml`, 'utf8')
|
|
45
|
-
content.includes('**/consumer.yaml')
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
if (content.includes('**/consumer.yaml')) {
|
|
53
|
+
pass('apply-nats-consumer.yml має правильний paths trigger')
|
|
54
|
+
} else {
|
|
55
|
+
fail('apply-nats-consumer.yml не містить paths: **/consumer.yaml')
|
|
56
|
+
}
|
|
48
57
|
}
|
|
49
58
|
|
|
50
59
|
if (existsSync('.vscode/extensions.json')) {
|
|
51
60
|
const ext = JSON.parse(await readFile('.vscode/extensions.json', 'utf8'))
|
|
52
|
-
ext.recommendations?.includes('github.vscode-github-actions')
|
|
53
|
-
|
|
54
|
-
|
|
61
|
+
if (ext.recommendations?.includes('github.vscode-github-actions')) {
|
|
62
|
+
pass('extensions.json містить github.vscode-github-actions')
|
|
63
|
+
} else {
|
|
64
|
+
fail('extensions.json не містить github.vscode-github-actions')
|
|
65
|
+
}
|
|
55
66
|
} else {
|
|
56
67
|
fail('.vscode/extensions.json не існує')
|
|
57
68
|
}
|
|
@@ -1,29 +1,39 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
2
|
import { readFile } from 'node:fs/promises'
|
|
3
3
|
|
|
4
|
+
import { pass } from './utils/pass.mjs'
|
|
5
|
+
|
|
4
6
|
/**
|
|
5
7
|
* Перевіряє відповідність проєкту правилам js-format.mdc
|
|
6
8
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
7
9
|
*/
|
|
8
10
|
export async function check() {
|
|
9
11
|
let exitCode = 0
|
|
10
|
-
const pass = msg => console.log(` ✅ ${msg}`)
|
|
11
12
|
const fail = msg => {
|
|
12
13
|
console.log(` ❌ ${msg}`)
|
|
13
14
|
exitCode = 1
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
const expectedKeys = [
|
|
17
|
-
'arrowParens',
|
|
18
|
-
'
|
|
18
|
+
'arrowParens',
|
|
19
|
+
'printWidth',
|
|
20
|
+
'bracketSpacing',
|
|
21
|
+
'bracketSameLine',
|
|
22
|
+
'semi',
|
|
23
|
+
'singleQuote',
|
|
24
|
+
'tabWidth',
|
|
25
|
+
'trailingComma',
|
|
26
|
+
'useTabs'
|
|
19
27
|
]
|
|
20
28
|
|
|
21
29
|
if (existsSync('.oxfmtrc.json')) {
|
|
22
30
|
const cfg = JSON.parse(await readFile('.oxfmtrc.json', 'utf8'))
|
|
23
31
|
const missing = expectedKeys.filter(k => !(k in cfg))
|
|
24
|
-
missing.length === 0
|
|
25
|
-
|
|
26
|
-
|
|
32
|
+
if (missing.length === 0) {
|
|
33
|
+
pass('.oxfmtrc.json містить всі обовʼязкові ключі')
|
|
34
|
+
} else {
|
|
35
|
+
fail(`.oxfmtrc.json відсутні ключі: ${missing.join(', ')}`)
|
|
36
|
+
}
|
|
27
37
|
|
|
28
38
|
if (cfg.semi !== false) fail('.oxfmtrc.json: semi має бути false')
|
|
29
39
|
if (cfg.singleQuote !== true) fail('.oxfmtrc.json: singleQuote має бути true')
|
|
@@ -36,18 +46,22 @@ export async function check() {
|
|
|
36
46
|
|
|
37
47
|
if (existsSync('.vscode/extensions.json')) {
|
|
38
48
|
const ext = JSON.parse(await readFile('.vscode/extensions.json', 'utf8'))
|
|
39
|
-
ext.recommendations?.includes('oxc.oxc-vscode')
|
|
40
|
-
|
|
41
|
-
|
|
49
|
+
if (ext.recommendations?.includes('oxc.oxc-vscode')) {
|
|
50
|
+
pass('extensions.json містить oxc.oxc-vscode')
|
|
51
|
+
} else {
|
|
52
|
+
fail('extensions.json не містить oxc.oxc-vscode')
|
|
53
|
+
}
|
|
42
54
|
} else {
|
|
43
55
|
fail('.vscode/extensions.json не існує')
|
|
44
56
|
}
|
|
45
57
|
|
|
46
58
|
if (existsSync('.vscode/settings.json')) {
|
|
47
59
|
const settings = JSON.parse(await readFile('.vscode/settings.json', 'utf8'))
|
|
48
|
-
settings['editor.formatOnSave'] === true
|
|
49
|
-
|
|
50
|
-
|
|
60
|
+
if (settings['editor.formatOnSave'] === true) {
|
|
61
|
+
pass('settings.json: editor.formatOnSave увімкнено')
|
|
62
|
+
} else {
|
|
63
|
+
fail('settings.json: editor.formatOnSave має бути true')
|
|
64
|
+
}
|
|
51
65
|
|
|
52
66
|
const fmtTypes = ['javascript', 'typescript', 'json', 'vue', 'css', 'html']
|
|
53
67
|
for (const t of fmtTypes) {
|