@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/mdc/js-format.mdc CHANGED
@@ -97,3 +97,7 @@ version: '1.3'
97
97
  Також потрібно прибрати якщо є в проекті модул @nitra/prettier-config та prettier та всі виклики prettier і налаштування для нього.
98
98
 
99
99
  Завжди пиши JSDoc до функцій та методів.
100
+
101
+ ## Перевірка
102
+
103
+ `npx @nitra/cursor check js-format`
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`
@@ -138,3 +138,7 @@ RUN NAMES=$(sed -nE '/^\s*[#;]/d; /^\s*$/d; s/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=.*
138
138
  }
139
139
  }
140
140
  ```
141
+
142
+ ## Перевірка
143
+
144
+ `npx @nitra/cursor check nginx-default-tpl`
@@ -12,3 +12,7 @@ version: '1.0'
12
12
  - demo/ - опціонально демо проект
13
13
  - npm/ - файли для npm модуля
14
14
  - package.json
15
+
16
+ ## Перевірка
17
+
18
+ `npx @nitra/cursor check npm-module`
package/mdc/spell.mdc CHANGED
@@ -1,14 +1,88 @@
1
1
  ---
2
2
  description: Перевірка правопису в текстових файлах
3
3
  alwaysApply: true
4
- version: '1.1'
4
+ version: '1.3'
5
5
  ---
6
6
 
7
- У корені проекту має бути `.cspell.json` і залежності для cspell у кореневому `package.json` (зазвичай `devDependencies`).
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`
@@ -1,9 +1,16 @@
1
1
  ---
2
2
  description: Правила стилів CSS та SCSS
3
3
  alwaysApply: true
4
- version: '1.0'
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.3.6",
4
- "description": "CLI для завантаження cursor-правил Nitra у локальний репозиторій",
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
- "nitra"
10
+ "n"
11
11
  ],
12
- "homepage": "https://github.com/nitra/cursor#readme",
12
+ "homepage": "https://github.com/n/cursor#readme",
13
13
  "bugs": {
14
- "url": "https://github.com/nitra/cursor/issues"
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/nitra/cursor.git"
20
+ "url": "git+https://github.com/n/cursor.git"
21
21
  },
22
22
  "bin": {
23
- "nitra-cursor": "./bin/nitra-cursor.js"
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
+ }