@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/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 +5 -1
- package/mdc/style-lint.mdc +4 -0
- 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-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
|
@@ -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`
|
package/mdc/style-lint.mdc
CHANGED
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.0",
|
|
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
|
+
}
|
|
@@ -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
|
+
}
|