@nitra/cursor 1.3.6 → 1.4.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/AGENTS.template.md +6 -0
- package/README.md +14 -14
- package/bin/n-cursor.js +684 -0
- package/mdc/bun.mdc +4 -0
- package/mdc/ga.mdc +4 -0
- package/mdc/js-format.mdc +4 -0
- package/mdc/js-lint.mdc +19 -1
- package/mdc/js-pino.mdc +7 -1
- package/mdc/nginx-default-tpl.mdc +4 -0
- package/mdc/npm-module.mdc +4 -0
- package/mdc/spell.mdc +87 -3
- package/mdc/style-lint.mdc +12 -1
- package/mdc/vue.mdc +4 -0
- package/package.json +9 -7
- package/scripts/check-bun.mjs +32 -0
- package/scripts/check-ga.mjs +60 -0
- package/scripts/check-js-format.mjs +80 -0
- package/scripts/check-js-lint.mjs +60 -0
- package/scripts/check-js-pino.mjs +39 -0
- package/scripts/check-nginx-default-tpl.mjs +59 -0
- package/scripts/check-npm-module.mjs +44 -0
- package/scripts/check-spell.mjs +57 -0
- package/scripts/check-style-lint.mjs +67 -0
- package/scripts/check-vue.mjs +72 -0
- package/skills/n-fix-cursor/SKILL.md +52 -0
- package/skills/n-lint/SKILL.md +26 -0
- package/skills/n-publish-telegram/SKILL.md +93 -0
- package/bin/nitra-cursor.js +0 -301
package/mdc/js-format.mdc
CHANGED
package/mdc/js-lint.mdc
CHANGED
|
@@ -85,7 +85,7 @@ jobs:
|
|
|
85
85
|
bunx eslint .
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
**Без дублювання CI:** якщо в `.github/workflows` уже є `lint.yml` з тими самими кроками `oxlint` або `eslint`, видали зайвий workflow — лінт JS має виконуватися в одному місці.
|
|
88
|
+
**Без дублювання CI:** якщо в `.github/workflows` уже є `lint.yml` з тими самими кроками `oxlint` або `eslint`, видали зайвий workflow, а саме `lint.yml` — лінт JS має виконуватися в одному місці.
|
|
89
89
|
|
|
90
90
|
У корені проєкту має бути `eslint.config.js` із розподілом директорій за типом коду:
|
|
91
91
|
|
|
@@ -100,3 +100,21 @@ export default [
|
|
|
100
100
|
```
|
|
101
101
|
|
|
102
102
|
У монорепо пакети з Vite (frontend) вкажи в секції `vue`, решту — у секції `node` у виклику `getConfig`.
|
|
103
|
+
|
|
104
|
+
## Додаткові js правила
|
|
105
|
+
|
|
106
|
+
Завжди додавай до package.json що підтримується 24+ версія node:
|
|
107
|
+
|
|
108
|
+
```json title="package.json"
|
|
109
|
+
"engines": {
|
|
110
|
+
"node": ">=24"
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Завжди код повинен використовувати синтаксис 24+ версії node
|
|
115
|
+
|
|
116
|
+
Завжди повинен використовуватися top level await
|
|
117
|
+
|
|
118
|
+
## Перевірка
|
|
119
|
+
|
|
120
|
+
`npx @nitra/cursor check js-lint`
|
package/mdc/js-pino.mdc
CHANGED
|
@@ -8,4 +8,10 @@ version: '1.0'
|
|
|
8
8
|
Якщо в проекті присутній @nitra/bunyan, то він повинен бути замінений на @nitra/pino.
|
|
9
9
|
|
|
10
10
|
В k8s/base/configmap.yaml повинен бути заданий OTEL_RESOURCE_ATTRIBUTES: 'service.name=<project_name>,service.namespace=<project_namespace>'
|
|
11
|
-
а в директоріях з kustomize повинні бути перевизначені значення OTEL_RESOURCE_ATTRIBUTES і в них service.namespace повинен відповідати namespace, в якому знаходиться дана директорія.
|
|
11
|
+
а в директоріях з kustomize повинні бути перевизначені значення OTEL_RESOURCE_ATTRIBUTES і в них service.namespace повинен відповідати namespace, в якому знаходиться дана директорія.
|
|
12
|
+
|
|
13
|
+
Configmap повинен бути з тією самою назвою, що і deployment, якщо у deployment тільки один configmap.
|
|
14
|
+
|
|
15
|
+
## Перевірка
|
|
16
|
+
|
|
17
|
+
`npx @nitra/cursor check js-pino`
|
package/mdc/npm-module.mdc
CHANGED
package/mdc/spell.mdc
CHANGED
|
@@ -1,14 +1,88 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Перевірка правопису в текстових файлах
|
|
3
3
|
alwaysApply: true
|
|
4
|
-
version: '1.
|
|
4
|
+
version: '1.3'
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
У корені
|
|
7
|
+
У корені проєкту має бути `.cspell.json` і залежності для cspell у кореневому `package.json` (зазвичай `devDependencies`).
|
|
8
|
+
|
|
9
|
+
У кореневому `package.json` додай скрипт CLI **cspell** (об’єднай з блоками з розділів нижче в одному файлі):
|
|
10
|
+
|
|
11
|
+
```json title="package.json"
|
|
12
|
+
"scripts": {
|
|
13
|
+
"lint-spell": "npx cspell ."
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Додай workflow `.github/workflows/lint-spell.yml`:
|
|
18
|
+
|
|
19
|
+
```yaml
|
|
20
|
+
name: Lint Spell
|
|
21
|
+
|
|
22
|
+
on:
|
|
23
|
+
push:
|
|
24
|
+
branches:
|
|
25
|
+
- dev
|
|
26
|
+
paths:
|
|
27
|
+
- '.cspell.json'
|
|
28
|
+
- '**/*.js'
|
|
29
|
+
- '**/*.ts'
|
|
30
|
+
- '**/*.vue'
|
|
31
|
+
- '**/*.html'
|
|
32
|
+
- '**/*.css'
|
|
33
|
+
- '**/*.scss'
|
|
34
|
+
- '**/*.less'
|
|
35
|
+
- '**/*.json'
|
|
36
|
+
- '**/*.jsonc'
|
|
37
|
+
- '**/*.yaml'
|
|
38
|
+
- '**/*.yml'
|
|
39
|
+
- '**/*.toml'
|
|
40
|
+
- '**/*.xml'
|
|
41
|
+
- '**/*.md'
|
|
42
|
+
- '**/*.mdс'
|
|
43
|
+
- '**/*.txt'
|
|
44
|
+
- '**/*.go'
|
|
45
|
+
- '**/*.py'
|
|
46
|
+
- '**/*.php'
|
|
47
|
+
|
|
48
|
+
pull_request:
|
|
49
|
+
branches:
|
|
50
|
+
- dev
|
|
51
|
+
|
|
52
|
+
concurrency:
|
|
53
|
+
group: ${{ github.ref }}-${{ github.workflow }}
|
|
54
|
+
cancel-in-progress: true
|
|
55
|
+
|
|
56
|
+
jobs:
|
|
57
|
+
cspell:
|
|
58
|
+
runs-on: ubuntu-latest
|
|
59
|
+
steps:
|
|
60
|
+
- uses: actions/checkout@v4
|
|
61
|
+
|
|
62
|
+
- uses: oven-sh/setup-bun@v2
|
|
63
|
+
|
|
64
|
+
- name: Cache Bun dependencies
|
|
65
|
+
uses: actions/cache@v4
|
|
66
|
+
with:
|
|
67
|
+
path: |
|
|
68
|
+
~/.bun/install/cache
|
|
69
|
+
node_modules
|
|
70
|
+
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
|
|
71
|
+
restore-keys: |
|
|
72
|
+
${{ runner.os }}-bun-
|
|
73
|
+
|
|
74
|
+
- name: Install dependencies
|
|
75
|
+
run: bun install --frozen-lockfile
|
|
76
|
+
|
|
77
|
+
- name: Cspell
|
|
78
|
+
run: bun run lint-spell
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Без дублювання CI:** якщо в `.github/workflows` уже є workflow з тими самими кроками `cspell`, видали зайвий — перевірка правопису має виконуватися в одному місці.
|
|
8
82
|
|
|
9
83
|
## Базовий варіант (без окремого словника української)
|
|
10
84
|
|
|
11
|
-
Якщо текст переважно англійською та достатньо словника nitra:
|
|
85
|
+
Якщо текст переважно англійською та достатньо корпоративного словника (@nitra/cspell-dict; у полі `language` cspell лишається тег `nitra`):
|
|
12
86
|
|
|
13
87
|
```json title=".cspell.json"
|
|
14
88
|
{
|
|
@@ -22,6 +96,9 @@ version: '1.1'
|
|
|
22
96
|
|
|
23
97
|
```json title="package.json"
|
|
24
98
|
{
|
|
99
|
+
"scripts": {
|
|
100
|
+
"lint-spell": "npx cspell ."
|
|
101
|
+
},
|
|
25
102
|
"devDependencies": {
|
|
26
103
|
"@nitra/cspell-dict": "^1.0.185"
|
|
27
104
|
}
|
|
@@ -36,6 +113,9 @@ version: '1.1'
|
|
|
36
113
|
|
|
37
114
|
```json title="package.json"
|
|
38
115
|
{
|
|
116
|
+
"scripts": {
|
|
117
|
+
"lint-spell": "npx cspell ."
|
|
118
|
+
},
|
|
39
119
|
"devDependencies": {
|
|
40
120
|
"@nitra/cspell-dict": "^1.0.185",
|
|
41
121
|
"@cspell/dict-uk-ua": "^4.0.6"
|
|
@@ -69,3 +149,7 @@ version: '1.1'
|
|
|
69
149
|
## Інші мови
|
|
70
150
|
|
|
71
151
|
Для іншої мови встанови відповідний пакет `@cspell/dict-*`, додай його `cspell-ext.json` у `import` і код мови в `language`. Огляд словників: [streetsidesoftware/cspell-dicts](https://github.com/streetsidesoftware/cspell-dicts).
|
|
152
|
+
|
|
153
|
+
## Перевірка
|
|
154
|
+
|
|
155
|
+
`npx @nitra/cursor check spell`
|
package/mdc/style-lint.mdc
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Правила стилів CSS та SCSS
|
|
3
3
|
alwaysApply: true
|
|
4
|
-
version: '1.
|
|
4
|
+
version: '1.1'
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## Генерація та редагування стилів (Cursor і інші агенти)
|
|
8
|
+
|
|
9
|
+
- **Джерело правил:** перед тим як писати або суттєво змінювати **`.css`**, **`.scss`** або стилі в **`.vue`**, переглянь у корені проєкту (і в релевантних пакетах монорепо, якщо є) поле **`stylelint`** у **`package.json`** (зокрема `extends`), наявні **`.stylelintrc.*`**, **`stylelint.config.*`** та **`.stylelintignore`**. Не покладайся на «типові» правила stylelint з пам’яті — дотримуйся **проєктного** **`@nitra/stylelint-config`** і будь-яких локальних доповнень у репозиторії.
|
|
10
|
+
- **Форматування** узгоджуй з **`n-js-format.mdc`** (oxfmt / `.oxfmtrc.json` для css, scss тощо), щоб форматер і stylelint не суперечили один одному.
|
|
11
|
+
- **Після змін:** запускай **`bun run lint-style`** (або `bunx stylelint` з тими ж glob-ами та прапорцями, що в скрипті та CI) і виправляй усе, що лишилось після auto-fix. Якщо в репозиторії є **`n-lint`** / навичка n-lint — можна пройтись повним набором `lint-*` з `package.json`.
|
|
12
|
+
- **Не розширюй винятки:** не додавай зайві **`stylelint-disable`** / вузькі придушення правил без потреби; краще змінити стилі під правила проєкту.
|
|
13
|
+
|
|
7
14
|
В файлі .vscode/extensions.json є налаштування для офіційного плагіну:
|
|
8
15
|
|
|
9
16
|
```json title=".vscode/extensions.json"
|
|
@@ -89,3 +96,7 @@ jobs:
|
|
|
89
96
|
```text title=".stylelintignore"
|
|
90
97
|
dist/
|
|
91
98
|
```
|
|
99
|
+
|
|
100
|
+
## Перевірка
|
|
101
|
+
|
|
102
|
+
`npx @nitra/cursor check style-lint`
|
package/mdc/vue.mdc
CHANGED
|
@@ -218,3 +218,7 @@ export default defineConfig({
|
|
|
218
218
|
Потрібно використовувати unplugin-auto-import для автоматичного імпортування компонентів, composables, utils та інших функцій і прибирати з Vue sfc імпорти які їх дублюють.
|
|
219
219
|
|
|
220
220
|
Потрібно використовувати vite-plugin-vue-layouts-next для автоматичного імпортування layout компонентів.
|
|
221
|
+
|
|
222
|
+
## Перевірка
|
|
223
|
+
|
|
224
|
+
`npx @nitra/cursor check vue`
|
package/package.json
CHANGED
|
@@ -1,30 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nitra/cursor",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "CLI для завантаження cursor-правил
|
|
3
|
+
"version": "1.4.1",
|
|
4
|
+
"description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
7
7
|
"cursor",
|
|
8
8
|
"cursor-rules",
|
|
9
9
|
"mdc",
|
|
10
|
-
"
|
|
10
|
+
"n"
|
|
11
11
|
],
|
|
12
|
-
"homepage": "https://github.com/
|
|
12
|
+
"homepage": "https://github.com/n/cursor#readme",
|
|
13
13
|
"bugs": {
|
|
14
|
-
"url": "https://github.com/
|
|
14
|
+
"url": "https://github.com/n/cursor/issues"
|
|
15
15
|
},
|
|
16
16
|
"license": "ISC",
|
|
17
17
|
"author": "v@nitra.ai",
|
|
18
18
|
"repository": {
|
|
19
19
|
"type": "git",
|
|
20
|
-
"url": "git+https://github.com/
|
|
20
|
+
"url": "git+https://github.com/n/cursor.git"
|
|
21
21
|
},
|
|
22
22
|
"bin": {
|
|
23
|
-
"
|
|
23
|
+
"n-cursor": "./bin/n-cursor.js"
|
|
24
24
|
},
|
|
25
25
|
"files": [
|
|
26
26
|
"mdc",
|
|
27
27
|
"bin",
|
|
28
|
+
"scripts",
|
|
29
|
+
"skills",
|
|
28
30
|
"AGENTS.template.md"
|
|
29
31
|
],
|
|
30
32
|
"type": "module",
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { readFile } from 'node:fs/promises'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Перевіряє відповідність проєкту правилам bun.mdc
|
|
6
|
+
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
7
|
+
*/
|
|
8
|
+
export async function check() {
|
|
9
|
+
let exitCode = 0
|
|
10
|
+
const pass = msg => console.log(` ✅ ${msg}`)
|
|
11
|
+
const fail = msg => {
|
|
12
|
+
console.log(` ❌ ${msg}`)
|
|
13
|
+
exitCode = 1
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const forbidden = ['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', '.yarnrc.yml']
|
|
17
|
+
for (const f of forbidden) {
|
|
18
|
+
existsSync(f) ? fail(`Знайдено заборонений файл: ${f} — видали його`) : pass(`Немає ${f}`)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
existsSync('.yarn') ? fail('Знайдено директорію .yarn — видали її') : pass('Немає .yarn/')
|
|
22
|
+
existsSync('bun.lock') ? pass('bun.lock є') : fail('Відсутній bun.lock — запусти bun i')
|
|
23
|
+
|
|
24
|
+
if (existsSync('package.json')) {
|
|
25
|
+
const pkg = JSON.parse(await readFile('package.json', 'utf8'))
|
|
26
|
+
pkg.packageManager
|
|
27
|
+
? fail(`package.json містить поле packageManager: "${pkg.packageManager}" — видали його`)
|
|
28
|
+
: pass('package.json не містить packageManager')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return exitCode
|
|
32
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { readdir, readFile } from 'node:fs/promises'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Перевіряє відповідність проєкту правилам ga.mdc
|
|
6
|
+
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
7
|
+
*/
|
|
8
|
+
export async function check() {
|
|
9
|
+
let exitCode = 0
|
|
10
|
+
const pass = msg => console.log(` ✅ ${msg}`)
|
|
11
|
+
const fail = msg => {
|
|
12
|
+
console.log(` ❌ ${msg}`)
|
|
13
|
+
exitCode = 1
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const wfDir = '.github/workflows'
|
|
17
|
+
|
|
18
|
+
if (!existsSync(wfDir)) {
|
|
19
|
+
fail(`Директорія ${wfDir} не існує`)
|
|
20
|
+
return exitCode
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const files = await readdir(wfDir)
|
|
24
|
+
|
|
25
|
+
const yamlFiles = files.filter(f => f.endsWith('.yaml'))
|
|
26
|
+
if (yamlFiles.length > 0) {
|
|
27
|
+
for (const f of yamlFiles) fail(`Workflow з розширенням .yaml: ${wfDir}/${f} — перейменуй на .yml`)
|
|
28
|
+
} else {
|
|
29
|
+
pass('Всі workflows мають розширення .yml')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
for (const f of ['clean-ga-workflows.yml', 'clean-merged-branch.yml']) {
|
|
33
|
+
files.includes(f) ? pass(`${f} існує`) : fail(`Відсутній ${wfDir}/${f}`)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (files.includes('apply-k8s.yml')) {
|
|
37
|
+
const content = await readFile(`${wfDir}/apply-k8s.yml`, 'utf8')
|
|
38
|
+
content.includes('**/k8s/*.yaml')
|
|
39
|
+
? pass('apply-k8s.yml має правильний paths trigger')
|
|
40
|
+
: fail('apply-k8s.yml не містить paths: **/k8s/*.yaml')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (files.includes('apply-nats-consumer.yml')) {
|
|
44
|
+
const content = await readFile(`${wfDir}/apply-nats-consumer.yml`, 'utf8')
|
|
45
|
+
content.includes('**/consumer.yaml')
|
|
46
|
+
? pass('apply-nats-consumer.yml має правильний paths trigger')
|
|
47
|
+
: fail('apply-nats-consumer.yml не містить paths: **/consumer.yaml')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (existsSync('.vscode/extensions.json')) {
|
|
51
|
+
const ext = JSON.parse(await readFile('.vscode/extensions.json', 'utf8'))
|
|
52
|
+
ext.recommendations?.includes('github.vscode-github-actions')
|
|
53
|
+
? pass('extensions.json містить github.vscode-github-actions')
|
|
54
|
+
: fail('extensions.json не містить github.vscode-github-actions')
|
|
55
|
+
} else {
|
|
56
|
+
fail('.vscode/extensions.json не існує')
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return exitCode
|
|
60
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { readFile } from 'node:fs/promises'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Перевіряє відповідність проєкту правилам js-format.mdc
|
|
6
|
+
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
7
|
+
*/
|
|
8
|
+
export async function check() {
|
|
9
|
+
let exitCode = 0
|
|
10
|
+
const pass = msg => console.log(` ✅ ${msg}`)
|
|
11
|
+
const fail = msg => {
|
|
12
|
+
console.log(` ❌ ${msg}`)
|
|
13
|
+
exitCode = 1
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const expectedKeys = [
|
|
17
|
+
'arrowParens', 'printWidth', 'bracketSpacing', 'bracketSameLine',
|
|
18
|
+
'semi', 'singleQuote', 'tabWidth', 'trailingComma', 'useTabs'
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
if (existsSync('.oxfmtrc.json')) {
|
|
22
|
+
const cfg = JSON.parse(await readFile('.oxfmtrc.json', 'utf8'))
|
|
23
|
+
const missing = expectedKeys.filter(k => !(k in cfg))
|
|
24
|
+
missing.length === 0
|
|
25
|
+
? pass('.oxfmtrc.json містить всі обовʼязкові ключі')
|
|
26
|
+
: fail(`.oxfmtrc.json відсутні ключі: ${missing.join(', ')}`)
|
|
27
|
+
|
|
28
|
+
if (cfg.semi !== false) fail('.oxfmtrc.json: semi має бути false')
|
|
29
|
+
if (cfg.singleQuote !== true) fail('.oxfmtrc.json: singleQuote має бути true')
|
|
30
|
+
if (cfg.tabWidth !== 2) fail('.oxfmtrc.json: tabWidth має бути 2')
|
|
31
|
+
if (cfg.useTabs !== false) fail('.oxfmtrc.json: useTabs має бути false')
|
|
32
|
+
if (cfg.printWidth !== 120) fail('.oxfmtrc.json: printWidth має бути 120')
|
|
33
|
+
} else {
|
|
34
|
+
fail('.oxfmtrc.json не існує — створи його')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (existsSync('.vscode/extensions.json')) {
|
|
38
|
+
const ext = JSON.parse(await readFile('.vscode/extensions.json', 'utf8'))
|
|
39
|
+
ext.recommendations?.includes('oxc.oxc-vscode')
|
|
40
|
+
? pass('extensions.json містить oxc.oxc-vscode')
|
|
41
|
+
: fail('extensions.json не містить oxc.oxc-vscode')
|
|
42
|
+
} else {
|
|
43
|
+
fail('.vscode/extensions.json не існує')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (existsSync('.vscode/settings.json')) {
|
|
47
|
+
const settings = JSON.parse(await readFile('.vscode/settings.json', 'utf8'))
|
|
48
|
+
settings['editor.formatOnSave'] === true
|
|
49
|
+
? pass('settings.json: editor.formatOnSave увімкнено')
|
|
50
|
+
: fail('settings.json: editor.formatOnSave має бути true')
|
|
51
|
+
|
|
52
|
+
const fmtTypes = ['javascript', 'typescript', 'json', 'vue', 'css', 'html']
|
|
53
|
+
for (const t of fmtTypes) {
|
|
54
|
+
const key = `[${t}]`
|
|
55
|
+
if (settings[key]?.['editor.defaultFormatter'] === 'oxc.oxc-vscode') {
|
|
56
|
+
pass(`settings.json: ${key} використовує oxc.oxc-vscode`)
|
|
57
|
+
} else {
|
|
58
|
+
fail(`settings.json: ${key} має використовувати oxc.oxc-vscode як defaultFormatter`)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
fail('.vscode/settings.json не існує')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const prettierFiles = ['.prettierrc', '.prettierrc.json', '.prettierrc.js', 'prettier.config.js', '.prettierrc.yml']
|
|
66
|
+
for (const f of prettierFiles) {
|
|
67
|
+
if (existsSync(f)) fail(`Знайдено конфіг prettier: ${f} — видали його`)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (existsSync('package.json')) {
|
|
71
|
+
const pkg = JSON.parse(await readFile('package.json', 'utf8'))
|
|
72
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies }
|
|
73
|
+
for (const dep of ['prettier', '@nitra/prettier-config']) {
|
|
74
|
+
if (allDeps[dep]) fail(`package.json містить залежність ${dep} — видали її`)
|
|
75
|
+
}
|
|
76
|
+
if (pkg.prettier) fail('package.json містить поле "prettier" — видали його')
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return exitCode
|
|
80
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { readFile } from 'node:fs/promises'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Перевіряє відповідність проєкту правилам js-lint.mdc
|
|
6
|
+
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
7
|
+
*/
|
|
8
|
+
export async function check() {
|
|
9
|
+
let exitCode = 0
|
|
10
|
+
const pass = msg => console.log(` ✅ ${msg}`)
|
|
11
|
+
const fail = msg => {
|
|
12
|
+
console.log(` ❌ ${msg}`)
|
|
13
|
+
exitCode = 1
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
existsSync('eslint.config.js')
|
|
17
|
+
? pass('eslint.config.js існує')
|
|
18
|
+
: existsSync('eslint.config.mjs')
|
|
19
|
+
? pass('eslint.config.mjs існує')
|
|
20
|
+
: fail('Відсутній eslint.config.js — створи його з getConfig від @nitra/eslint-config')
|
|
21
|
+
|
|
22
|
+
if (existsSync('package.json')) {
|
|
23
|
+
const pkg = JSON.parse(await readFile('package.json', 'utf8'))
|
|
24
|
+
|
|
25
|
+
pkg.scripts?.['lint-js']
|
|
26
|
+
? pass('package.json містить скрипт lint-js')
|
|
27
|
+
: fail('package.json не містить скрипт "lint-js" — додай: "oxlint --fix && bunx eslint --fix ."')
|
|
28
|
+
|
|
29
|
+
pkg.devDependencies?.['@nitra/eslint-config']
|
|
30
|
+
? pass('@nitra/eslint-config є в devDependencies')
|
|
31
|
+
: fail('@nitra/eslint-config відсутній в devDependencies — додай: bun add -d @nitra/eslint-config')
|
|
32
|
+
|
|
33
|
+
const nodeEngine = pkg.engines?.node
|
|
34
|
+
if (nodeEngine) {
|
|
35
|
+
const match = nodeEngine.match(/(\d+)/)
|
|
36
|
+
if (match && Number(match[1]) >= 24) {
|
|
37
|
+
pass(`engines.node: "${nodeEngine}"`)
|
|
38
|
+
} else {
|
|
39
|
+
fail(`engines.node: "${nodeEngine}" — має бути >=24`)
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
fail('package.json не містить engines.node — додай: "engines": { "node": ">=24" }')
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (existsSync('.github/workflows/lint-js.yml')) {
|
|
47
|
+
const content = await readFile('.github/workflows/lint-js.yml', 'utf8')
|
|
48
|
+
pass('lint-js.yml існує')
|
|
49
|
+
content.includes('oxlint') ? pass('lint-js.yml містить oxlint') : fail('lint-js.yml не містить oxlint')
|
|
50
|
+
content.includes('eslint') ? pass('lint-js.yml містить eslint') : fail('lint-js.yml не містить eslint')
|
|
51
|
+
} else {
|
|
52
|
+
fail('.github/workflows/lint-js.yml не існує — створи його')
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
for (const dup of ['.eslintrc', '.eslintrc.js', '.eslintrc.json', '.eslintrc.yml']) {
|
|
56
|
+
if (existsSync(dup)) fail(`Знайдено застарілий конфіг ESLint: ${dup} — видали, використовуй eslint.config.js`)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return exitCode
|
|
60
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { readFile } from 'node:fs/promises'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Перевіряє відповідність проєкту правилам js-pino.mdc
|
|
6
|
+
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
7
|
+
*/
|
|
8
|
+
export async function check() {
|
|
9
|
+
let exitCode = 0
|
|
10
|
+
const pass = msg => console.log(` ✅ ${msg}`)
|
|
11
|
+
const fail = msg => {
|
|
12
|
+
console.log(` ❌ ${msg}`)
|
|
13
|
+
exitCode = 1
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (existsSync('package.json')) {
|
|
17
|
+
const pkg = JSON.parse(await readFile('package.json', 'utf8'))
|
|
18
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies }
|
|
19
|
+
|
|
20
|
+
if (allDeps['@nitra/bunyan']) fail('@nitra/bunyan знайдено — замінити на @nitra/pino')
|
|
21
|
+
if (allDeps.bunyan) fail('bunyan знайдено — замінити на @nitra/pino')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (existsSync('k8s/base/configmap.yaml')) {
|
|
25
|
+
const content = await readFile('k8s/base/configmap.yaml', 'utf8')
|
|
26
|
+
if (content.includes('OTEL_RESOURCE_ATTRIBUTES')) {
|
|
27
|
+
pass('k8s/base/configmap.yaml містить OTEL_RESOURCE_ATTRIBUTES')
|
|
28
|
+
if (content.includes('service.name=') && content.includes('service.namespace=')) {
|
|
29
|
+
pass('OTEL_RESOURCE_ATTRIBUTES містить service.name та service.namespace')
|
|
30
|
+
} else {
|
|
31
|
+
fail('OTEL_RESOURCE_ATTRIBUTES має містити service.name=<name>,service.namespace=<namespace>')
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
fail('k8s/base/configmap.yaml не містить OTEL_RESOURCE_ATTRIBUTES')
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return exitCode
|
|
39
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { readFile } from 'node:fs/promises'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Перевіряє відповідність проєкту правилам nginx-default-tpl.mdc
|
|
6
|
+
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
7
|
+
*/
|
|
8
|
+
export async function check() {
|
|
9
|
+
let exitCode = 0
|
|
10
|
+
const pass = msg => console.log(` ✅ ${msg}`)
|
|
11
|
+
const fail = msg => {
|
|
12
|
+
console.log(` ❌ ${msg}`)
|
|
13
|
+
exitCode = 1
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (existsSync('default.tpl.conf')) {
|
|
17
|
+
fail('default.tpl.conf існує — перейменуй на default.conf.template')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const tplLocations = ['default.conf.template', 'nginx/default.conf.template', 'docker/default.conf.template']
|
|
21
|
+
const found = tplLocations.find(f => existsSync(f))
|
|
22
|
+
|
|
23
|
+
if (found) {
|
|
24
|
+
pass(`${found} існує`)
|
|
25
|
+
const content = await readFile(found, 'utf8')
|
|
26
|
+
|
|
27
|
+
content.includes('listen 8080')
|
|
28
|
+
? pass('Nginx слухає порт 8080')
|
|
29
|
+
: fail(`${found}: має містити listen 8080`)
|
|
30
|
+
|
|
31
|
+
content.includes('/healthz')
|
|
32
|
+
? pass('Є location /healthz')
|
|
33
|
+
: fail(`${found}: відсутній location /healthz`)
|
|
34
|
+
|
|
35
|
+
content.includes('gzip_static on')
|
|
36
|
+
? pass('gzip_static увімкнено')
|
|
37
|
+
: fail(`${found}: має містити gzip_static on`)
|
|
38
|
+
|
|
39
|
+
if (content.includes('proxy_pass')) {
|
|
40
|
+
fail(`${found} містить proxy_pass — перенеси проксі-логіку до HTTPRoute в k8s`)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (existsSync('.vscode/extensions.json')) {
|
|
45
|
+
const ext = JSON.parse(await readFile('.vscode/extensions.json', 'utf8'))
|
|
46
|
+
ext.recommendations?.includes('ahmadalli.vscode-nginx-conf')
|
|
47
|
+
? pass('extensions.json містить ahmadalli.vscode-nginx-conf')
|
|
48
|
+
: fail('extensions.json не містить ahmadalli.vscode-nginx-conf')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (existsSync('.vscode/settings.json')) {
|
|
52
|
+
const s = JSON.parse(await readFile('.vscode/settings.json', 'utf8'))
|
|
53
|
+
s['[nginx]']?.['editor.defaultFormatter'] === 'ahmadalli.vscode-nginx-conf'
|
|
54
|
+
? pass('settings.json: nginx formatter налаштовано')
|
|
55
|
+
: fail('settings.json: [nginx] defaultFormatter має бути ahmadalli.vscode-nginx-conf')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return exitCode
|
|
59
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { readFile, stat } from 'node:fs/promises'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Перевіряє відповідність проєкту правилам npm-module.mdc
|
|
6
|
+
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
7
|
+
*/
|
|
8
|
+
export async function check() {
|
|
9
|
+
let exitCode = 0
|
|
10
|
+
const pass = msg => console.log(` ✅ ${msg}`)
|
|
11
|
+
const fail = msg => {
|
|
12
|
+
console.log(` ❌ ${msg}`)
|
|
13
|
+
exitCode = 1
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
existsSync('package.json') ? pass('package.json існує') : fail('package.json не існує')
|
|
17
|
+
|
|
18
|
+
if (existsSync('npm')) {
|
|
19
|
+
const s = await stat('npm')
|
|
20
|
+
s.isDirectory() ? pass('npm/ директорія існує') : fail('npm має бути директорією')
|
|
21
|
+
} else {
|
|
22
|
+
fail('npm/ директорія не існує')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (existsSync('package.json')) {
|
|
26
|
+
const pkg = JSON.parse(await readFile('package.json', 'utf8'))
|
|
27
|
+
const ws = pkg.workspaces
|
|
28
|
+
if (Array.isArray(ws) && ws.includes('npm')) {
|
|
29
|
+
pass('package.json workspaces містить "npm"')
|
|
30
|
+
} else {
|
|
31
|
+
fail('package.json workspaces має містити "npm"')
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
existsSync('npm/package.json')
|
|
36
|
+
? pass('npm/package.json існує')
|
|
37
|
+
: fail('npm/package.json не існує — створи package.json для npm модуля')
|
|
38
|
+
|
|
39
|
+
existsSync('.github/workflows')
|
|
40
|
+
? pass('.github/workflows/ існує')
|
|
41
|
+
: fail('.github/workflows/ не існує')
|
|
42
|
+
|
|
43
|
+
return exitCode
|
|
44
|
+
}
|