@nitra/cursor 1.3.6 → 1.4.0

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
@@ -8,7 +8,7 @@ version: '1.1'
8
8
 
9
9
  ## Базовий варіант (без окремого словника української)
10
10
 
11
- Якщо текст переважно англійською та достатньо словника nitra:
11
+ Якщо текст переважно англійською та достатньо корпоративного словника (@nitra/cspell-dict; у полі `language` cspell лишається тег `nitra`):
12
12
 
13
13
  ```json title=".cspell.json"
14
14
  {
@@ -69,3 +69,7 @@ version: '1.1'
69
69
  ## Інші мови
70
70
 
71
71
  Для іншої мови встанови відповідний пакет `@cspell/dict-*`, додай його `cspell-ext.json` у `import` і код мови в `language`. Огляд словників: [streetsidesoftware/cspell-dicts](https://github.com/streetsidesoftware/cspell-dicts).
72
+
73
+ ## Перевірка
74
+
75
+ `npx @nitra/cursor check spell`
@@ -89,3 +89,7 @@ jobs:
89
89
  ```text title=".stylelintignore"
90
90
  dist/
91
91
  ```
92
+
93
+ ## Перевірка
94
+
95
+ `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.0",
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
+ }
@@ -0,0 +1,57 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { readFile } from 'node:fs/promises'
3
+
4
+ /**
5
+ * Перевіряє відповідність проєкту правилам spell.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('.cspell.json')) {
17
+ const cfg = JSON.parse(await readFile('.cspell.json', 'utf8'))
18
+
19
+ cfg.version === '0.2'
20
+ ? pass('.cspell.json version: 0.2')
21
+ : fail('.cspell.json version має бути "0.2"')
22
+
23
+ cfg.language
24
+ ? pass(`.cspell.json language: "${cfg.language}"`)
25
+ : fail('.cspell.json не містить поле language')
26
+
27
+ const imports = cfg.import || []
28
+ imports.some(i => i.includes('@nitra/cspell-dict'))
29
+ ? pass('.cspell.json імпортує @nitra/cspell-dict')
30
+ : fail('.cspell.json не імпортує @nitra/cspell-dict/cspell-ext.json')
31
+
32
+ Array.isArray(cfg.ignorePaths)
33
+ ? pass('.cspell.json містить ignorePaths')
34
+ : fail('.cspell.json не містить ignorePaths')
35
+ } else {
36
+ fail('.cspell.json не існує — створи його')
37
+ }
38
+
39
+ if (existsSync('package.json')) {
40
+ const pkg = JSON.parse(await readFile('package.json', 'utf8'))
41
+ const devDeps = pkg.devDependencies || {}
42
+
43
+ devDeps['@nitra/cspell-dict']
44
+ ? pass('@nitra/cspell-dict є в devDependencies')
45
+ : fail('@nitra/cspell-dict відсутній — bun add -d @nitra/cspell-dict')
46
+
47
+ if (existsSync('.cspell.json')) {
48
+ const cfg = JSON.parse(await readFile('.cspell.json', 'utf8'))
49
+ const hasUkImport = (cfg.import || []).some(i => i.includes('@cspell/dict-uk-ua'))
50
+ if (hasUkImport && !devDeps['@cspell/dict-uk-ua']) {
51
+ fail('.cspell.json імпортує @cspell/dict-uk-ua, але пакет відсутній в devDependencies')
52
+ }
53
+ }
54
+ }
55
+
56
+ return exitCode
57
+ }
@@ -0,0 +1,67 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { readFile } from 'node:fs/promises'
3
+
4
+ /**
5
+ * Перевіряє відповідність проєкту правилам style-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
+ if (existsSync('package.json')) {
17
+ const pkg = JSON.parse(await readFile('package.json', 'utf8'))
18
+
19
+ pkg.scripts?.['lint-style']
20
+ ? pass('package.json містить скрипт lint-style')
21
+ : fail('package.json не містить скрипт "lint-style"')
22
+
23
+ pkg.devDependencies?.['@nitra/stylelint-config']
24
+ ? pass('@nitra/stylelint-config є в devDependencies')
25
+ : fail('@nitra/stylelint-config відсутній — bun add -d @nitra/stylelint-config')
26
+
27
+ const stylelintCfg = pkg.stylelint
28
+ if (stylelintCfg?.extends === '@nitra/stylelint-config') {
29
+ pass('package.json stylelint extends @nitra/stylelint-config')
30
+ } else if (existsSync('.stylelintrc.json') || existsSync('.stylelintrc.js') || existsSync('stylelint.config.js')) {
31
+ pass('Окремий файл конфігу stylelint існує')
32
+ } else {
33
+ fail('Немає конфігу stylelint — додай "stylelint": { "extends": "@nitra/stylelint-config" } до package.json')
34
+ }
35
+ }
36
+
37
+ existsSync('.stylelintignore')
38
+ ? pass('.stylelintignore існує')
39
+ : fail('.stylelintignore не існує — створи з вмістом: dist/')
40
+
41
+ if (existsSync('.github/workflows/lint-style.yml')) {
42
+ const content = await readFile('.github/workflows/lint-style.yml', 'utf8')
43
+ pass('lint-style.yml існує')
44
+ content.includes('stylelint')
45
+ ? pass('lint-style.yml містить stylelint')
46
+ : fail('lint-style.yml не містить виклик stylelint')
47
+ } else {
48
+ fail('.github/workflows/lint-style.yml не існує — створи його')
49
+ }
50
+
51
+ if (existsSync('.vscode/extensions.json')) {
52
+ const ext = JSON.parse(await readFile('.vscode/extensions.json', 'utf8'))
53
+ ext.recommendations?.includes('stylelint.vscode-stylelint')
54
+ ? pass('extensions.json містить stylelint.vscode-stylelint')
55
+ : fail('extensions.json не містить stylelint.vscode-stylelint')
56
+ } else {
57
+ fail('.vscode/extensions.json не існує')
58
+ }
59
+
60
+ if (existsSync('.vscode/settings.json')) {
61
+ const s = JSON.parse(await readFile('.vscode/settings.json', 'utf8'))
62
+ s['css.validate'] === false ? pass('css.validate вимкнено') : fail('settings.json: css.validate має бути false')
63
+ s['scss.validate'] === false ? pass('scss.validate вимкнено') : fail('settings.json: scss.validate має бути false')
64
+ }
65
+
66
+ return exitCode
67
+ }