@nitra/cursor 1.6.27 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -12
- package/bin/n-cursor.js +50 -34
- package/github-actions/setup-bun-deps/action.yml +29 -0
- package/mdc/bun.mdc +8 -2
- package/mdc/docker.mdc +11 -11
- package/mdc/ga.mdc +7 -25
- package/mdc/js-lint.mdc +7 -19
- package/mdc/k8s.mdc +29 -9
- package/mdc/npm-module.mdc +4 -4
- package/mdc/style-lint.mdc +1 -1
- package/mdc/text.mdc +6 -23
- package/mdc/vue.mdc +4 -0
- package/package.json +3 -2
- package/schemas/n-cursor.json +1 -1
- package/scripts/check-bun.mjs +43 -0
- package/scripts/check-docker.mjs +2 -2
- package/scripts/check-ga.mjs +11 -1
- package/scripts/check-k8s.mjs +7 -5
- package/scripts/check-npm-module.mjs +1 -1
- package/scripts/check-text.mjs +40 -0
- package/scripts/cli-entry.mjs +27 -0
- package/scripts/ensure-nitra-cursor-dev-dependencies.mjs +99 -0
- package/scripts/run-docker.mjs +6 -3
- package/scripts/run-k8s.mjs +8 -5
- package/scripts/run-v8r.mjs +32 -14
- package/scripts/sync-setup-bun-deps-action.mjs +36 -0
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## Як це працює
|
|
6
6
|
|
|
7
|
-
Репозиторій `@nitra/cursor` містить cursor-правила у директорії `mdc/`. CLI
|
|
7
|
+
Репозиторій `@nitra/cursor` містить cursor-правила у директорії `mdc/`. CLI копіює обрані правила з **каталогу `mdc/` того пакету, з якого виконується `bin/n-cursor.js`**: після `npm i` / `bun add` це зазвичай `node_modules/@nitra/cursor/mdc`; при **`npx @nitra/cursor`** пакет потрапляє в **кеш npx/npm**, і правила читаються з тієї розпакованої копії (у корені проєкту залежність не обов’язкова). Жодних окремих HTTP-запитів до CDN для файлів правил немає — лише те, що вже є в tarball пакету.
|
|
8
8
|
|
|
9
9
|
Наприклад, правило `mdc/js-format.mdc` буде збережено як `.cursor/rules/n-js-format.mdc`.
|
|
10
10
|
|
|
@@ -28,16 +28,7 @@
|
|
|
28
28
|
| `npm-module` | Структура репозиторію для npm-модуля (bun mono) |
|
|
29
29
|
| `text` | Текстові файли: cspell, CI |
|
|
30
30
|
|
|
31
|
-
Щоб
|
|
32
|
-
|
|
33
|
-
```json
|
|
34
|
-
{
|
|
35
|
-
"$schema": "https://unpkg.com/@nitra/cursor/schemas/n-cursor.json",
|
|
36
|
-
"version": "2.5.0",
|
|
37
|
-
"rules": ["js-format", "text"],
|
|
38
|
-
"skills": ["fix"]
|
|
39
|
-
}
|
|
40
|
-
```
|
|
31
|
+
Щоб використовувати конкретну версію правил, оновіть залежність `@nitra/cursor` у проєкті (`bun add -d @nitra/cursor@<версія>` тощо). Поле `version` у `.n-cursor.json`, якщо воно лишилось у старих конфігах, **ігнорується**.
|
|
41
32
|
|
|
42
33
|
### v8r і власний каталог схем
|
|
43
34
|
|
|
@@ -57,7 +48,7 @@ CLI автоматично (команда завантаження правил
|
|
|
57
48
|
|
|
58
49
|
1. Знайде або створить `.n-cursor.json` у поточній директорії (із полем `$schema` на JSON Schema пакету; якщо файл уже є без коректного `$schema`, поле буде додано або оновлено при зчитуванні конфігу)
|
|
59
50
|
2. Створить директорію `.cursor/rules/`, якщо її ще немає
|
|
60
|
-
3.
|
|
51
|
+
3. Скопіює кожне з перелічених у конфігу правило з `mdc/` установленого пакету і збереже файли з префіксом `n-`
|
|
61
52
|
4. Після оновлення файлів на диску згенерує в корені проєкту **`AGENTS.md`**: повний вміст береться з шаблону пакету `AGENTS.template.md`, а список правил у шаблоні формується з **усіх наявних файлів `*.mdc`** у `.cursor/rules/` (відсортовано за ім’ям)
|
|
62
53
|
|
|
63
54
|
## Приклад виводу
|
package/bin/n-cursor.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* Якщо у корені репозиторію немає .n-cursor.json, спочатку перейменовується за наявності nitra-cursor.json;
|
|
13
13
|
* у `.cursor/rules` файли `nitra-*.mdc` перейменовуються на `n-*.mdc`; інакше конфіг створюється автоматично
|
|
14
14
|
* з усіма правилами з каталогу mdc пакету (їх можна відредагувати після створення). У файлі завжди має бути
|
|
15
|
-
* поле `$schema` з посиланням на JSON Schema
|
|
15
|
+
* поле `$schema` з посиланням на JSON Schema пакету (публічний URL для IDE); при зчитуванні конфігу воно додається або виправляється на диску, якщо відсутнє або некоректне.
|
|
16
16
|
*
|
|
17
17
|
* Файл AGENTS.md у корені: щоразу повністю перезаписується змістом з AGENTS.template.md
|
|
18
18
|
* пакету; список правил у шаблоні будується з файлів *.mdc у .cursor/rules поточного проєкту.
|
|
@@ -20,10 +20,16 @@
|
|
|
20
20
|
* Після завантаження: у .cursor/rules видаляються файли *.mdc з префіксом «n-» (керовані
|
|
21
21
|
* пакетом), яких немає у списку rules у .n-cursor.json. Інші .mdc у цій директорії залишаються.
|
|
22
22
|
*
|
|
23
|
+
* Composite GitHub Action `.github/actions/setup-bun-deps/action.yml` копіюється з каталогу
|
|
24
|
+
* `github-actions/` пакету при кожному успішному синку (workflows з правил ga / js-lint / text).
|
|
25
|
+
*
|
|
23
26
|
* Skills копіюються з npm/skills пакету лише для id з масиву «skills» у .n-cursor.json
|
|
24
27
|
* (у JSON — без префікса, каталоги в проєкті — n-<id>, як і для правил). Якщо ключа skills
|
|
25
28
|
* немає, за замовчуванням підтягуються всі bundled skills з префіксом n- у пакеті.
|
|
26
29
|
* Зайві каталоги n-* у .cursor/skills, яких немає у списку, видаляються.
|
|
30
|
+
*
|
|
31
|
+
* Якщо в корені є package.json і в ньому ще немає \@nitra/cursor у devDependencies (і не оголошено
|
|
32
|
+
* в dependencies), CLI дописує devDependencies з діапазоном ^<version> поточного пакету — зручно після npx.
|
|
27
33
|
*/
|
|
28
34
|
|
|
29
35
|
import { existsSync } from 'node:fs'
|
|
@@ -32,11 +38,16 @@ import { basename, dirname, join } from 'node:path'
|
|
|
32
38
|
import { cwd } from 'node:process'
|
|
33
39
|
import { fileURLToPath } from 'node:url'
|
|
34
40
|
|
|
41
|
+
import {
|
|
42
|
+
ensureNitraCursorInRootDevDependencies,
|
|
43
|
+
readBundledPackageVersion
|
|
44
|
+
} from '../scripts/ensure-nitra-cursor-dev-dependencies.mjs'
|
|
45
|
+
import { syncSetupBunDepsAction } from '../scripts/sync-setup-bun-deps-action.mjs'
|
|
46
|
+
|
|
35
47
|
const PACKAGE_NAME = '@nitra/cursor'
|
|
36
|
-
const UNPKG_BASE = 'https://unpkg.com'
|
|
37
48
|
const CONFIG_FILE = '.n-cursor.json'
|
|
38
|
-
/** URL JSON Schema для `.n-cursor.json` (
|
|
39
|
-
const CONFIG_SCHEMA_URL =
|
|
49
|
+
/** Публічний URL JSON Schema для поля `$schema` у `.n-cursor.json` (IDE); вміст правил CLI читає лише з диска пакету */
|
|
50
|
+
const CONFIG_SCHEMA_URL = 'https://unpkg.com/@nitra/cursor/schemas/n-cursor.json'
|
|
40
51
|
const AGENTS_FILE = 'AGENTS.md'
|
|
41
52
|
const AGENTS_TEMPLATE_FILE = 'AGENTS.template.md'
|
|
42
53
|
const RULES_DIR = '.cursor/rules'
|
|
@@ -48,6 +59,8 @@ const BUNDLED_MDC_DIR = join(binDir, '..', 'mdc')
|
|
|
48
59
|
const BUNDLED_SCRIPTS_DIR = join(binDir, '..', 'scripts')
|
|
49
60
|
const BUNDLED_SKILLS_DIR = join(binDir, '..', 'skills')
|
|
50
61
|
const BUNDLED_AGENTS_TEMPLATE_PATH = join(binDir, '..', AGENTS_TEMPLATE_FILE)
|
|
62
|
+
/** Корінь установленого пакету (каталог з `mdc/`, `github-actions/`, …) */
|
|
63
|
+
const BUNDLED_PACKAGE_ROOT = join(binDir, '..')
|
|
51
64
|
|
|
52
65
|
/**
|
|
53
66
|
* Імена правил (без .mdc) з каталогу mdc поточної інсталяції пакету
|
|
@@ -87,19 +100,6 @@ async function discoverBundledSkillNames() {
|
|
|
87
100
|
.toSorted((a, b) => a.localeCompare(b))
|
|
88
101
|
}
|
|
89
102
|
|
|
90
|
-
/**
|
|
91
|
-
* Завантажує текст з URL
|
|
92
|
-
* @param {string} url адреса HTTP(S)
|
|
93
|
-
* @returns {Promise<string>} тіло відповіді як UTF-8 текст
|
|
94
|
-
*/
|
|
95
|
-
async function fetchText(url) {
|
|
96
|
-
const response = await fetch(url)
|
|
97
|
-
if (!response.ok) {
|
|
98
|
-
throw new Error(`HTTP ${response.status} — не вдалося завантажити: ${url}`)
|
|
99
|
-
}
|
|
100
|
-
return response.text()
|
|
101
|
-
}
|
|
102
|
-
|
|
103
103
|
/**
|
|
104
104
|
* Перейменовує у каталозі правил файли `nitra-*.mdc` → `n-*.mdc`. Якщо `n-*.mdc` уже є, застарілий файл видаляється.
|
|
105
105
|
* @param {string} rulesDir абсолютний шлях до `.cursor/rules`
|
|
@@ -148,7 +148,7 @@ async function migrateLegacyConfigIfNeeded() {
|
|
|
148
148
|
|
|
149
149
|
/**
|
|
150
150
|
* Зчитує конфіг .n-cursor.json з поточної директорії
|
|
151
|
-
* @returns {Promise<{ $schema: string, rules: string[], skills: string[], version?: string } & Record<string, unknown>>} rules, skills (id без префікса n-)
|
|
151
|
+
* @returns {Promise<{ $schema: string, rules: string[], skills: string[], version?: string } & Record<string, unknown>>} rules, skills (id без префікса n-); поле version у файлі за наявності ігнорується при синхронізації правил
|
|
152
152
|
*/
|
|
153
153
|
async function readConfig() {
|
|
154
154
|
await migrateLegacyConfigIfNeeded()
|
|
@@ -191,18 +191,6 @@ async function readConfig() {
|
|
|
191
191
|
return config
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
-
/**
|
|
195
|
-
* Повертає URL для завантаження правила з unpkg
|
|
196
|
-
* @param {string} ruleName - ім'я без розширення, наприклад "js-format"
|
|
197
|
-
* @param {string} [version] - версія пакету (необов'язково, за замовчуванням "latest")
|
|
198
|
-
* @returns {string} повний URL файлу правила на unpkg
|
|
199
|
-
*/
|
|
200
|
-
function buildUrl(ruleName, version) {
|
|
201
|
-
const name = ruleName.endsWith('.mdc') ? ruleName : `${ruleName}.mdc`
|
|
202
|
-
const ver = version ? `@${version}` : '@latest'
|
|
203
|
-
return `${UNPKG_BASE}/${PACKAGE_NAME}${ver}/mdc/${name}`
|
|
204
|
-
}
|
|
205
|
-
|
|
206
194
|
/**
|
|
207
195
|
* Витягує чисте ім'я файлу правила (без шляху, але зберігає .mdc)
|
|
208
196
|
* "npm/mdc/js-format.mdc" → "js-format.mdc"
|
|
@@ -215,6 +203,22 @@ function normalizeRuleName(ruleName) {
|
|
|
215
203
|
return basename(name)
|
|
216
204
|
}
|
|
217
205
|
|
|
206
|
+
/**
|
|
207
|
+
* Читає вміст правила з каталогу `mdc/` установленого пакету (наприклад `node_modules/@nitra/cursor/mdc` або кеш npx).
|
|
208
|
+
* @param {string} rule елемент масиву rules з `.n-cursor.json`
|
|
209
|
+
* @returns {Promise<string>} текст правила для запису в `.cursor/rules/n-*.mdc`
|
|
210
|
+
*/
|
|
211
|
+
function readBundledRuleContent(rule) {
|
|
212
|
+
const bundledName = normalizeRuleName(rule)
|
|
213
|
+
const bundledPath = join(BUNDLED_MDC_DIR, bundledName)
|
|
214
|
+
if (!existsSync(bundledPath)) {
|
|
215
|
+
throw new Error(
|
|
216
|
+
`Немає файлу ${bundledName} у ${BUNDLED_MDC_DIR}. Оновіть ${PACKAGE_NAME} або приберіть "${rule}" з rules у ${CONFIG_FILE}.`
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
return readFile(bundledPath, 'utf8')
|
|
220
|
+
}
|
|
221
|
+
|
|
218
222
|
/**
|
|
219
223
|
* Нормалізує id skill з конфігу до форми без префікса n- (як «fix»)
|
|
220
224
|
* @param {string} skillName елемент масиву skills або ім'я каталогу
|
|
@@ -618,7 +622,7 @@ async function runChecks(requestedRules) {
|
|
|
618
622
|
}
|
|
619
623
|
|
|
620
624
|
/**
|
|
621
|
-
*
|
|
625
|
+
* Копіює правила з каталогу `mdc/` установленого пакету та синхронізує `.cursor/rules`
|
|
622
626
|
* @returns {Promise<void>}
|
|
623
627
|
*/
|
|
624
628
|
async function runSync() {
|
|
@@ -633,12 +637,24 @@ async function runSync() {
|
|
|
633
637
|
}
|
|
634
638
|
|
|
635
639
|
const { rules, skills, version } = config
|
|
640
|
+
const bundledVer = await readBundledPackageVersion()
|
|
641
|
+
if (bundledVer) {
|
|
642
|
+
console.log(`📦 Джерело правил: ${PACKAGE_NAME}@${bundledVer}`)
|
|
643
|
+
}
|
|
636
644
|
if (version) {
|
|
637
|
-
console.log(
|
|
645
|
+
console.log(`⚠️ Поле "version" у ${CONFIG_FILE} ігнорується; правила беруться з установленого пакету.\n`)
|
|
638
646
|
}
|
|
639
647
|
console.log(`📋 Правил до завантаження: ${rules.length}`)
|
|
640
648
|
console.log(`📋 Skills до синхронізації: ${skills.length}`)
|
|
641
649
|
|
|
650
|
+
try {
|
|
651
|
+
const { destPath } = await syncSetupBunDepsAction(cwd(), BUNDLED_PACKAGE_ROOT)
|
|
652
|
+
console.log(`📝 Оновлено ${destPath} (composite setup-bun-deps з пакету)\n`)
|
|
653
|
+
} catch (error) {
|
|
654
|
+
console.error(`❌ Не вдалося записати setup-bun-deps action: ${error.message}`)
|
|
655
|
+
throw error
|
|
656
|
+
}
|
|
657
|
+
|
|
642
658
|
const rulesDir = join(cwd(), RULES_DIR)
|
|
643
659
|
await mkdir(rulesDir, { recursive: true })
|
|
644
660
|
|
|
@@ -646,13 +662,12 @@ async function runSync() {
|
|
|
646
662
|
let failCount = 0
|
|
647
663
|
|
|
648
664
|
for (const rule of rules) {
|
|
649
|
-
const url = buildUrl(rule, version)
|
|
650
665
|
const fileName = `${RULE_PREFIX}${normalizeRuleName(rule)}`
|
|
651
666
|
const destPath = join(rulesDir, fileName)
|
|
652
667
|
|
|
653
668
|
try {
|
|
654
669
|
process.stdout.write(` ⬇ ${rule} → ${RULES_DIR}/${fileName} ... `)
|
|
655
|
-
const content = await
|
|
670
|
+
const content = await readBundledRuleContent(rule)
|
|
656
671
|
await writeFile(destPath, content, 'utf8')
|
|
657
672
|
console.log(`✅`)
|
|
658
673
|
successCount++
|
|
@@ -713,6 +728,7 @@ async function runSync() {
|
|
|
713
728
|
const [command, ...args] = process.argv.slice(2)
|
|
714
729
|
|
|
715
730
|
try {
|
|
731
|
+
await ensureNitraCursorInRootDevDependencies(cwd())
|
|
716
732
|
if (command === 'check') {
|
|
717
733
|
await runChecks(args)
|
|
718
734
|
} else {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: Setup Bun dependencies
|
|
2
|
+
description: Checkout, Node 24, Bun, cache та bun install --frozen-lockfile
|
|
3
|
+
|
|
4
|
+
runs:
|
|
5
|
+
using: composite
|
|
6
|
+
steps:
|
|
7
|
+
- uses: actions/checkout@v6
|
|
8
|
+
with:
|
|
9
|
+
persist-credentials: false
|
|
10
|
+
|
|
11
|
+
- uses: actions/setup-node@v6
|
|
12
|
+
with:
|
|
13
|
+
node-version: '24'
|
|
14
|
+
|
|
15
|
+
- uses: oven-sh/setup-bun@v2
|
|
16
|
+
|
|
17
|
+
- name: Cache Bun dependencies
|
|
18
|
+
uses: actions/cache@v5
|
|
19
|
+
with:
|
|
20
|
+
path: |
|
|
21
|
+
~/.bun/install/cache
|
|
22
|
+
node_modules
|
|
23
|
+
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
|
|
24
|
+
restore-keys: |
|
|
25
|
+
${{ runner.os }}-bun-
|
|
26
|
+
|
|
27
|
+
- name: Install dependencies
|
|
28
|
+
run: bun install --frozen-lockfile
|
|
29
|
+
shell: bash
|
package/mdc/bun.mdc
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Bun як єдиний package manager у монорепо
|
|
3
3
|
alwaysApply: true
|
|
4
|
-
version: '1.
|
|
4
|
+
version: '1.3'
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
Проект використовує тільки Bun для керування залежностями та запуску скриптів.
|
|
@@ -62,10 +62,14 @@ FROM oven/bun:alpine AS build-env
|
|
|
62
62
|
В Github actions bun повинен налаштований так:
|
|
63
63
|
|
|
64
64
|
```yaml
|
|
65
|
+
- uses: actions/setup-node@v6
|
|
66
|
+
with:
|
|
67
|
+
node-version: '24'
|
|
68
|
+
|
|
65
69
|
- uses: oven-sh/setup-bun@v2
|
|
66
70
|
|
|
67
71
|
- name: Cache Bun dependencies
|
|
68
|
-
uses: actions/cache@
|
|
72
|
+
uses: actions/cache@v5
|
|
69
73
|
with:
|
|
70
74
|
path: |
|
|
71
75
|
~/.bun/install/cache
|
|
@@ -87,3 +91,5 @@ FROM oven/bun:alpine AS build-env
|
|
|
87
91
|
Якщо в кореневому @package.json існують скрипти з префіксом `lint-`, обов'язково створюй `lint` скрипт, який буде запускати всі ці скрипти з лінт-префіксом.
|
|
88
92
|
|
|
89
93
|
У кінці скрипта `lint` додай `&& oxfmt .`.
|
|
94
|
+
|
|
95
|
+
Якщо в **`.n-cursor.json`** у масиві **`rules`** є **`docker`**, у кореневому `package.json` **обов'язково** скрипт **`lint-docker`** (див. **`docker.mdc`**) і рядок **`bun run lint-docker`** у **`lint`**. Якщо є **`k8s`** — **обов'язково** **`lint-k8s`** і **`bun run lint-k8s`** у **`lint`** (див. **`k8s.mdc`**). Перевірка — **`npx @nitra/cursor check bun`**.
|
package/mdc/docker.mdc
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Dockerfile — lint-docker / hadolint; перевірка check-docker
|
|
3
|
-
version: '1.
|
|
3
|
+
version: '1.6'
|
|
4
4
|
globs: "**/Dockerfile*"
|
|
5
5
|
alwaysApply: false
|
|
6
6
|
---
|
|
@@ -30,6 +30,8 @@ CLI **`hadolint`** приймає лише **явні шляхи** (`[DOCKERFILE
|
|
|
30
30
|
}
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
+
Якщо правило **`docker`** підключено в **`.n-cursor.json`** (масив **`rules`**), у **кореневому** `package.json` **обов'язково** мають бути скрипт **`lint-docker`** і виклик **`bun run lint-docker`** у агрегованому **`lint`** (див. **`bun.mdc`**). Це перевіряє **`npx @nitra/cursor check bun`**.
|
|
34
|
+
|
|
33
35
|
Додай workflow **`.github/workflows/lint-docker.yml`** (гілка **`dev`**, лише **`.yml`**, узгоджено з **`ga.mdc`**):
|
|
34
36
|
|
|
35
37
|
```yaml title=".github/workflows/lint-docker.yml"
|
|
@@ -43,12 +45,6 @@ on:
|
|
|
43
45
|
- '**/Dockerfile'
|
|
44
46
|
- '**/*.Dockerfile'
|
|
45
47
|
- '**/*.dockerfile'
|
|
46
|
-
- '.hadolint.yaml'
|
|
47
|
-
- 'npm/scripts/run-docker.mjs'
|
|
48
|
-
- 'npm/scripts/utils/docker-hadolint.mjs'
|
|
49
|
-
- 'npm/scripts/check-docker.mjs'
|
|
50
|
-
- 'npm/mdc/docker.mdc'
|
|
51
|
-
- 'package.json'
|
|
52
48
|
|
|
53
49
|
pull_request:
|
|
54
50
|
branches:
|
|
@@ -64,7 +60,7 @@ jobs:
|
|
|
64
60
|
permissions:
|
|
65
61
|
contents: read
|
|
66
62
|
steps:
|
|
67
|
-
- uses: actions/checkout@
|
|
63
|
+
- uses: actions/checkout@v6
|
|
68
64
|
with:
|
|
69
65
|
persist-credentials: false
|
|
70
66
|
|
|
@@ -74,10 +70,14 @@ jobs:
|
|
|
74
70
|
chmod +x /tmp/hadolint
|
|
75
71
|
sudo mv /tmp/hadolint /usr/local/bin/hadolint
|
|
76
72
|
|
|
73
|
+
- uses: actions/setup-node@v6
|
|
74
|
+
with:
|
|
75
|
+
node-version: '24'
|
|
76
|
+
|
|
77
77
|
- uses: oven-sh/setup-bun@v2
|
|
78
78
|
|
|
79
79
|
- name: Cache Bun dependencies
|
|
80
|
-
uses: actions/cache@
|
|
80
|
+
uses: actions/cache@v5
|
|
81
81
|
with:
|
|
82
82
|
path: |
|
|
83
83
|
~/.bun/install/cache
|
|
@@ -95,7 +95,7 @@ jobs:
|
|
|
95
95
|
|
|
96
96
|
Узгоджуй версію hadolint **v2.12.0** з **`HADOLINT_IMAGE`** у **`npm/scripts/utils/docker-hadolint.mjs`**.
|
|
97
97
|
|
|
98
|
-
Кореневий скрипт **`lint`** (див. **`n-bun.mdc`**)
|
|
98
|
+
Кореневий скрипт **`lint`** (див. **`n-bun.mdc`**) **обов'язково** містить **`bun run lint-docker`**, коли в проєкті підключено правило **`docker`**.
|
|
99
99
|
|
|
100
100
|
## Запуск
|
|
101
101
|
|
|
@@ -119,6 +119,6 @@ jobs:
|
|
|
119
119
|
|
|
120
120
|
`npx @nitra/cursor check docker`
|
|
121
121
|
|
|
122
|
-
Після змін у Dockerfile: **`bun run lint-docker
|
|
122
|
+
Після змін у Dockerfile: **`bun run lint-docker`** (обов'язково для проєктів з правилом **`docker`** у **`.n-cursor.json`**).
|
|
123
123
|
|
|
124
124
|
Kubernetes YAML і `$schema` у `k8s/` — окреме правило **`k8s.mdc`**, **`check k8s`**.
|
package/mdc/ga.mdc
CHANGED
|
@@ -26,7 +26,7 @@ jobs:
|
|
|
26
26
|
contents: read
|
|
27
27
|
steps:
|
|
28
28
|
- name: Delete workflow runs
|
|
29
|
-
uses: dmvict/clean-workflow-runs@v1
|
|
29
|
+
uses: dmvict/clean-workflow-runs@v1
|
|
30
30
|
with:
|
|
31
31
|
token: ${{ github.token }}
|
|
32
32
|
save_period: 31
|
|
@@ -55,7 +55,7 @@ jobs:
|
|
|
55
55
|
steps:
|
|
56
56
|
- id: delete_stuff
|
|
57
57
|
name: Delete those pesky dead branches
|
|
58
|
-
uses: phpdocker-io/github-actions-delete-abandoned-branches@v2
|
|
58
|
+
uses: phpdocker-io/github-actions-delete-abandoned-branches@v2.0.3
|
|
59
59
|
with:
|
|
60
60
|
github_token: ${{ github.token }}
|
|
61
61
|
last_commit_age_days: 90
|
|
@@ -80,11 +80,8 @@ on:
|
|
|
80
80
|
branches:
|
|
81
81
|
- dev
|
|
82
82
|
paths:
|
|
83
|
+
- '.github/actions/**'
|
|
83
84
|
- '.github/workflows/**'
|
|
84
|
-
- '.github/zizmor.yml'
|
|
85
|
-
- 'package.json'
|
|
86
|
-
- 'bun.lock'
|
|
87
|
-
|
|
88
85
|
pull_request:
|
|
89
86
|
branches:
|
|
90
87
|
- dev
|
|
@@ -99,31 +96,16 @@ jobs:
|
|
|
99
96
|
permissions:
|
|
100
97
|
contents: read
|
|
101
98
|
steps:
|
|
102
|
-
- uses: actions/
|
|
103
|
-
with:
|
|
104
|
-
persist-credentials: false
|
|
99
|
+
- uses: ./.github/actions/setup-bun-deps
|
|
105
100
|
|
|
106
|
-
- uses:
|
|
107
|
-
|
|
108
|
-
- name: Cache Bun dependencies
|
|
109
|
-
uses: actions/cache@v4
|
|
110
|
-
with:
|
|
111
|
-
path: |
|
|
112
|
-
~/.bun/install/cache
|
|
113
|
-
node_modules
|
|
114
|
-
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
|
|
115
|
-
restore-keys: |
|
|
116
|
-
${{ runner.os }}-bun-
|
|
117
|
-
|
|
118
|
-
- name: Install dependencies
|
|
119
|
-
run: bun install --frozen-lockfile
|
|
120
|
-
|
|
121
|
-
- uses: astral-sh/setup-uv@v6
|
|
101
|
+
- uses: astral-sh/setup-uv@v8.0.0
|
|
122
102
|
|
|
123
103
|
- name: Lint GA
|
|
124
104
|
run: bun run lint-ga
|
|
125
105
|
```
|
|
126
106
|
|
|
107
|
+
Composite **`.github/actions/setup-bun-deps/action.yml`**: **`actions/checkout@v6`** (`persist-credentials: false`), **`actions/setup-node@v6`** (**Node 24**), **Bun**, **`actions/cache@v5`** і **`bun install --frozen-lockfile`**. У job достатньо **`- uses: ./.github/actions/setup-bun-deps`**.
|
|
108
|
+
|
|
127
109
|
```json title=".vscode/extensions.json"
|
|
128
110
|
{
|
|
129
111
|
"recommendations": ["github.vscode-github-actions"]
|
package/mdc/js-lint.mdc
CHANGED
|
@@ -71,7 +71,6 @@ on:
|
|
|
71
71
|
- '**/*.tsx'
|
|
72
72
|
- '**/*.vue'
|
|
73
73
|
- '**/eslint.config.*'
|
|
74
|
-
- '.jscpd.json'
|
|
75
74
|
|
|
76
75
|
pull_request:
|
|
77
76
|
branches:
|
|
@@ -87,24 +86,7 @@ jobs:
|
|
|
87
86
|
permissions:
|
|
88
87
|
contents: read
|
|
89
88
|
steps:
|
|
90
|
-
- uses: actions/
|
|
91
|
-
with:
|
|
92
|
-
persist-credentials: false
|
|
93
|
-
|
|
94
|
-
- uses: oven-sh/setup-bun@v2
|
|
95
|
-
|
|
96
|
-
- name: Cache Bun dependencies
|
|
97
|
-
uses: actions/cache@v4
|
|
98
|
-
with:
|
|
99
|
-
path: |
|
|
100
|
-
~/.bun/install/cache
|
|
101
|
-
node_modules
|
|
102
|
-
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
|
|
103
|
-
restore-keys: |
|
|
104
|
-
${{ runner.os }}-bun-
|
|
105
|
-
|
|
106
|
-
- name: Install dependencies
|
|
107
|
-
run: bun install --frozen-lockfile
|
|
89
|
+
- uses: ./.github/actions/setup-bun-deps
|
|
108
90
|
|
|
109
91
|
- name: Eslint
|
|
110
92
|
run: |
|
|
@@ -113,6 +95,8 @@ jobs:
|
|
|
113
95
|
bunx jscpd .
|
|
114
96
|
```
|
|
115
97
|
|
|
98
|
+
Composite **`.github/actions/setup-bun-deps/action.yml`** — checkout, Node 24, Bun, кеш і `bun install --frozen-lockfile` (див. **ga.mdc**).
|
|
99
|
+
|
|
116
100
|
Один workflow на лінт JS; зайвий `lint.yml` з тими самими кроками — прибери.
|
|
117
101
|
|
|
118
102
|
```javascript title="eslint.config.js"
|
|
@@ -140,6 +124,10 @@ export default [
|
|
|
140
124
|
}
|
|
141
125
|
```
|
|
142
126
|
|
|
127
|
+
## Тести
|
|
128
|
+
|
|
129
|
+
Проекту повинен бути покритий unit тестами за допомогою Bun test.
|
|
130
|
+
|
|
143
131
|
**Код:** синтаксис Node **24+**, **top level await**.
|
|
144
132
|
|
|
145
133
|
## Перевірка
|
package/mdc/k8s.mdc
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: K8s YAML — $schema (yaml-language-server); lint-k8s (kubeconform, kubescape); check-k8s
|
|
3
|
-
version: '1.
|
|
3
|
+
version: '1.10'
|
|
4
4
|
globs: "**/k8s/**/*.{yaml,yml}"
|
|
5
5
|
alwaysApply: false
|
|
6
6
|
---
|
|
@@ -42,6 +42,8 @@ alwaysApply: false
|
|
|
42
42
|
}
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
+
Якщо правило **`k8s`** підключено в **`.n-cursor.json`** (масив **`rules`**), у **кореневому** `package.json` **обов'язково** мають бути скрипт **`lint-k8s`** і виклик **`bun run lint-k8s`** у агрегованому **`lint`** (див. **`bun.mdc`**). Це перевіряє **`npx @nitra/cursor check bun`**.
|
|
46
|
+
|
|
45
47
|
Шлях до скрипта підстав свій (`./scripts/…` після копіювання, `node_modules/@nitra/cursor/scripts/…` якщо пакет у залежностях).
|
|
46
48
|
|
|
47
49
|
Додай workflow **`.github/workflows/lint-k8s.yml`** (гілка **`dev`**, лише **`.yml`**, узгоджено з **`ga.mdc`**):
|
|
@@ -70,7 +72,7 @@ jobs:
|
|
|
70
72
|
permissions:
|
|
71
73
|
contents: read
|
|
72
74
|
steps:
|
|
73
|
-
- uses: actions/checkout@
|
|
75
|
+
- uses: actions/checkout@v6
|
|
74
76
|
with:
|
|
75
77
|
persist-credentials: false
|
|
76
78
|
|
|
@@ -84,10 +86,14 @@ jobs:
|
|
|
84
86
|
curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash
|
|
85
87
|
echo "$HOME/.kubescape/bin" >> $GITHUB_PATH
|
|
86
88
|
|
|
89
|
+
- uses: actions/setup-node@v6
|
|
90
|
+
with:
|
|
91
|
+
node-version: '24'
|
|
92
|
+
|
|
87
93
|
- uses: oven-sh/setup-bun@v2
|
|
88
94
|
|
|
89
95
|
- name: Cache Bun dependencies
|
|
90
|
-
uses: actions/cache@
|
|
96
|
+
uses: actions/cache@v5
|
|
91
97
|
with:
|
|
92
98
|
path: |
|
|
93
99
|
~/.bun/install/cache
|
|
@@ -105,20 +111,20 @@ jobs:
|
|
|
105
111
|
|
|
106
112
|
Після **`install.sh`** kubescape може опинитися поза стандартним PATH; за потреби додай крок **`echo "$HOME/.kubescape/bin" >> $GITHUB_PATH`** (див. документацію [kubescape](https://github.com/kubescape/kubescape#readme)).
|
|
107
113
|
|
|
108
|
-
Кореневий скрипт **`lint`** (див. **`n-bun.mdc`**)
|
|
114
|
+
Кореневий скрипт **`lint`** (див. **`n-bun.mdc`**) **обов'язково** містить **`bun run lint-k8s`**, коли в проєкті підключено правило **`k8s`**.
|
|
109
115
|
|
|
110
116
|
## Перевірка
|
|
111
117
|
|
|
112
118
|
**`npx @nitra/cursor check k8s`** — узгодженість першого рядка (`$schema`), один рядок `# yaml-language-server` на файл, правило для `.yml`, відповідність URL першому YAML-документу (до `---`) за логікою нижче. Якщо під `k8s` немає yaml/yml — перевірку пропущено. Синтаксис YAML і зміст маніфесту скрипт не перевіряє — вручну.
|
|
113
119
|
|
|
114
|
-
Після змін у маніфестах: **`bun run lint-k8s`** (kubeconform + kubescape
|
|
120
|
+
Після змін у маніфестах: **`bun run lint-k8s`** (kubeconform + kubescape; обов'язково для проєктів з правилом **`k8s`** у **`.n-cursor.json`**).
|
|
115
121
|
|
|
116
122
|
## Що закодовано в `check-k8s.mjs`
|
|
117
123
|
|
|
118
|
-
При зміні правил синхронно оновлюй **`YANNH_PIN`**, **`YANNH_REF`** (якщо зміниться гілка за замовчуванням у репо yannh), **`YANNH_GROUPS`**, а в **`run-k8s.mjs`** — константу **`KUBERNETES_VERSION`** (
|
|
124
|
+
При зміні правил синхронно оновлюй **`YANNH_PIN`**, **`YANNH_REF`** (якщо зміниться гілка за замовчуванням у репо yannh), **`YANNH_GROUPS`**, **`DATREE_CRD_BASE`** (GitHub Pages каталогу CRD), а в **`run-k8s.mjs`** — константу **`KUBERNETES_VERSION`** і **`DATREE_CRD_SCHEMA_LOCATION`** (узгоджено з базою datree у цьому правилі).
|
|
119
125
|
|
|
120
126
|
- Обхід з пропуском `node_modules`, `.git`, `dist`, `coverage`, `.turbo`, `.next`.
|
|
121
|
-
- **`file:`** у `$schema` — URL до apiVersion/kind не звіряється; **`https:`** — kustomization за іменем файлу → Schema Store; `v1` → yannh; група з `YANNH_GROUPS` → yannh; інакше → datree.
|
|
127
|
+
- **`file:`** у `$schema` — URL до apiVersion/kind не звіряється; **`https:`** — kustomization за іменем файлу → Schema Store; `v1` → yannh; група з `YANNH_GROUPS` → yannh; інакше → datree (GitHub Pages).
|
|
122
128
|
|
|
123
129
|
## Коли застосовувати (агентам)
|
|
124
130
|
|
|
@@ -136,8 +142,22 @@ jobs:
|
|
|
136
142
|
3. **`apiVersion: group/version`** і **group** у **`YANNH_GROUPS`** у скрипті → yannh:
|
|
137
143
|
`https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/<PIN>/<kind>-<group-з-крапками-як-дефіси>-<version>.json`
|
|
138
144
|
Приклади: `apps/v1` + `Deployment` → `deployment-apps-v1.json`; `networking.k8s.io/v1` + `Ingress` → `ingress-networking-k8s-io-v1.json`.
|
|
139
|
-
4. **Інакше** (CRD тощо) → [datreeio/CRDs-catalog](https://github.com/datreeio/CRDs-catalog):
|
|
140
|
-
`https://
|
|
145
|
+
4. **Інакше** (CRD тощо) → [datreeio/CRDs-catalog](https://github.com/datreeio/CRDs-catalog) через **GitHub Pages** (канон для `$schema` у редакторі):
|
|
146
|
+
`https://datreeio.github.io/CRDs-catalog/<group>/<kind>_<version>.json`
|
|
147
|
+
(`<kind>` — лише літери та цифри в нижньому регістрі, без роздільників між CamelCase, як для yannh.)
|
|
148
|
+
|
|
149
|
+
**Приклад (Gateway API):** `apiVersion: gateway.networking.k8s.io/v1beta1`, `kind: HTTPRoute`:
|
|
150
|
+
|
|
151
|
+
```yaml
|
|
152
|
+
# yaml-language-server: $schema=https://datreeio.github.io/CRDs-catalog/gateway.networking.k8s.io/httproute_v1beta1.json
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Приклад (GKE):** `apiVersion: networking.gke.io/v1`, `kind: HealthCheckPolicy`:
|
|
156
|
+
|
|
157
|
+
```yaml
|
|
158
|
+
# yaml-language-server: $schema=https://datreeio.github.io/CRDs-catalog/networking.gke.io/healthcheckpolicy_v1.json
|
|
159
|
+
```
|
|
160
|
+
|
|
141
161
|
5. **Немає надійного публічного URL** — не вигадуй: залиш коректний `$schema` або `file:` за узгодженням.
|
|
142
162
|
|
|
143
163
|
## Багатодокументні YAML
|
package/mdc/npm-module.mdc
CHANGED
|
@@ -8,7 +8,7 @@ Bun monorepo: workspace **`npm/`**, кореневий **`package.json`**, **`.g
|
|
|
8
8
|
|
|
9
9
|
## npm publish
|
|
10
10
|
|
|
11
|
-
**`npm-publish.yml`:** push у **`main`**, **`paths
|
|
11
|
+
**`npm-publish.yml`:** push у **`main`**, **`on.push.paths`** з **`npm/**`**, **`JS-DevTools/npm-publish@v4.1.5`**, **`with.package: npm/package.json`**, **`permissions.id-token: write`** (OIDC на npm).
|
|
12
12
|
|
|
13
13
|
```yaml title=".github/workflows/npm-publish.yml"
|
|
14
14
|
name: npm-publish
|
|
@@ -32,17 +32,17 @@ jobs:
|
|
|
32
32
|
id-token: write # КРИТИЧНО для OIDC!
|
|
33
33
|
|
|
34
34
|
steps:
|
|
35
|
-
- uses: actions/checkout@
|
|
35
|
+
- uses: actions/checkout@v6
|
|
36
36
|
with:
|
|
37
37
|
persist-credentials: false
|
|
38
38
|
|
|
39
|
-
- uses: actions/setup-node@
|
|
39
|
+
- uses: actions/setup-node@v6
|
|
40
40
|
with:
|
|
41
41
|
node-version: '24' # includes npm@11.6.0
|
|
42
42
|
registry-url: 'https://registry.npmjs.org'
|
|
43
43
|
|
|
44
44
|
- name: Publish package
|
|
45
|
-
uses: JS-DevTools/npm-publish@v4
|
|
45
|
+
uses: JS-DevTools/npm-publish@v4.1.5
|
|
46
46
|
with:
|
|
47
47
|
package: npm/package.json
|
|
48
48
|
```
|
package/mdc/style-lint.mdc
CHANGED
package/mdc/text.mdc
CHANGED
|
@@ -18,7 +18,7 @@ version: '1.22'
|
|
|
18
18
|
}
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
**`package.json`:** скрипт **`lint-text`** і devDependencies **`@nitra/cspell-dict
|
|
21
|
+
**`package.json`:** скрипт **`lint-text`** і devDependencies **`@nitra/cspell-dict`**. Для української додай **`@cspell/dict-uk-ua`**. **`v8r`** лише через **`bun x v8r`** (зазвичай **`bunx v8r`**), не в devDependencies. Окремий пакет **`markdownlint`** не потрібний.
|
|
22
22
|
|
|
23
23
|
У v8r **немає** прапорця тихого режиму; рекомендовано скрипт **`run-v8r.mjs`** з репозиторію пакета `@nitra/cursor` (`npm/scripts/run-v8r.mjs`): один виклик у `lint-text` — під капотом послідовні **`bun x v8r`** для кожного типу (**json**, **json5**, **yml**, **yaml**, **toml**), бо один процес v8r з кількома глобами падає з **98**, якщо хоч один glob порожній, і тоді інші розширення не перевіряються. Вивід при кодах **0** і **98** не показується. Каталог схем **`schemas/v8r-catalog.json`** пакета `@nitra/cursor` скрипт підставляє в v8r сам. За бажання можна передати власні glob-и аргументами скрипта. Шлях до скрипта: `./npm/scripts/…`, `./scripts/…` після копіювання, або `node_modules/@nitra/cursor/scripts/…`.
|
|
24
24
|
|
|
@@ -72,10 +72,6 @@ on:
|
|
|
72
72
|
branches:
|
|
73
73
|
- dev
|
|
74
74
|
paths:
|
|
75
|
-
- '.cspell.json'
|
|
76
|
-
- '.gitignore'
|
|
77
|
-
- '.markdownlint-cli2.jsonc'
|
|
78
|
-
- '.v8rignore'
|
|
79
75
|
- '**/*.js'
|
|
80
76
|
- '**/*.ts'
|
|
81
77
|
- '**/*.vue'
|
|
@@ -111,29 +107,14 @@ jobs:
|
|
|
111
107
|
permissions:
|
|
112
108
|
contents: read
|
|
113
109
|
steps:
|
|
114
|
-
- uses: actions/
|
|
115
|
-
with:
|
|
116
|
-
persist-credentials: false
|
|
117
|
-
|
|
118
|
-
- uses: oven-sh/setup-bun@v2
|
|
119
|
-
|
|
120
|
-
- name: Cache Bun dependencies
|
|
121
|
-
uses: actions/cache@v4
|
|
122
|
-
with:
|
|
123
|
-
path: |
|
|
124
|
-
~/.bun/install/cache
|
|
125
|
-
node_modules
|
|
126
|
-
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
|
|
127
|
-
restore-keys: |
|
|
128
|
-
${{ runner.os }}-bun-
|
|
129
|
-
|
|
130
|
-
- name: Install dependencies
|
|
131
|
-
run: bun install --frozen-lockfile
|
|
110
|
+
- uses: ./.github/actions/setup-bun-deps
|
|
132
111
|
|
|
133
112
|
- name: Lint text
|
|
134
113
|
run: bun run lint-text
|
|
135
114
|
```
|
|
136
115
|
|
|
116
|
+
Composite **`.github/actions/setup-bun-deps/action.yml`** — checkout, Node 24, Bun, кеш і `bun install --frozen-lockfile` (див. **ga.mdc**).
|
|
117
|
+
|
|
137
118
|
Не дублюй окремий workflow з тими самими кроками cspell/markdownlint.
|
|
138
119
|
|
|
139
120
|
**`.cspell.json`**, `version: "0.2"`, **`language`**, **`import`** з `@nitra/cspell-dict`, **`ignorePaths`**, **`words`** лише для назв/термінів, коли не виправити текстом.
|
|
@@ -201,6 +182,8 @@ jobs:
|
|
|
201
182
|
|
|
202
183
|
Підлаштуй `language` під проєкт (наприклад додай `ru-ru`, якщо потрібна перевірка російською). Порядок у `import` може впливати на пріоритет словників — тримай корпоративний `@nitra/cspell-dict` там, де зручно для ваших правил.
|
|
203
184
|
|
|
185
|
+
**Український апостроф:** у словах не використовуй прямий символ `'` (U+0027); потрібен типографський апостроф `’` (U+2019). Якщо після цього cspell досі підсвічує слово як невідоме — додай його до масиву `words` у `.cspell.json`.
|
|
186
|
+
|
|
204
187
|
## Локальні виключення (cspell `words`)
|
|
205
188
|
|
|
206
189
|
Коли **cspell** підсвічує слово, спочатку **виправ текст**, а не розширюй словник:
|
package/mdc/vue.mdc
CHANGED
|
@@ -207,6 +207,10 @@ export default defineConfig({
|
|
|
207
207
|
})
|
|
208
208
|
```
|
|
209
209
|
|
|
210
|
+
## Тести
|
|
211
|
+
|
|
212
|
+
Проекту повинен бути покритий тестами E2E за допомогою Playwright.
|
|
213
|
+
|
|
210
214
|
### Висновок
|
|
211
215
|
|
|
212
216
|
Дотримуючись цих практик і правил, можна будувати масштабовані, підтримувані та ефективні застосунки на Vue 3 з Composition API. Завжди звіряйся з офіційною документацією Vue 3 щодо оновлень і нових можливостей.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nitra/cursor",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"files": [
|
|
26
26
|
"mdc",
|
|
27
27
|
"bin",
|
|
28
|
+
"github-actions",
|
|
28
29
|
"schemas",
|
|
29
30
|
"scripts",
|
|
30
31
|
"skills",
|
|
@@ -32,7 +33,7 @@
|
|
|
32
33
|
],
|
|
33
34
|
"type": "module",
|
|
34
35
|
"scripts": {
|
|
35
|
-
"test": "
|
|
36
|
+
"test": "bun test tests"
|
|
36
37
|
},
|
|
37
38
|
"engines": {
|
|
38
39
|
"node": ">=24"
|
package/schemas/n-cursor.json
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"version": {
|
|
31
31
|
"type": "string",
|
|
32
|
-
"description": "
|
|
32
|
+
"description": "Застаріле поле, ігнорується CLI. Правила завжди копіюються з каталогу mdc/ установленого пакету (node_modules або кеш npx); змініть версію через оновлення залежності."
|
|
33
33
|
}
|
|
34
34
|
},
|
|
35
35
|
"required": ["rules"]
|
package/scripts/check-bun.mjs
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
* Очікує наявність `bun.lock`, забороняє lockfile та артефакти yarn/pnpm, директорію `.yarn`
|
|
5
5
|
* і поле `packageManager` у кореневому `package.json`.
|
|
6
6
|
*
|
|
7
|
+
* Якщо в `.n-cursor.json` у `rules` є `docker` або `k8s`, вимагає у кореневому `package.json`
|
|
8
|
+
* відповідно скриптів `lint-docker` / `lint-k8s` (див. docker.mdc, k8s.mdc).
|
|
9
|
+
*
|
|
7
10
|
* Якщо в кореневому `package.json` є скрипти з префіксом `lint-`, перевіряє наявність агрегованого
|
|
8
11
|
* скрипта `lint`, у якому через `bun run <ім’я>` викликаються всі такі скрипти, і що рядок `lint`
|
|
9
12
|
* закінчується на `&& oxfmt .`.
|
|
@@ -13,6 +16,26 @@ import { readFile } from 'node:fs/promises'
|
|
|
13
16
|
|
|
14
17
|
import { pass } from './utils/pass.mjs'
|
|
15
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Зчитує ідентифікатори правил з `.n-cursor.json` (поле `rules`).
|
|
21
|
+
* @returns {Promise<Set<string>>} множина рядків id правил або порожня, якщо файлу/поля немає
|
|
22
|
+
*/
|
|
23
|
+
async function loadNCursorRules() {
|
|
24
|
+
if (!existsSync('.n-cursor.json')) {
|
|
25
|
+
return new Set()
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const raw = JSON.parse(await readFile('.n-cursor.json', 'utf8'))
|
|
29
|
+
const list = raw?.rules
|
|
30
|
+
if (!Array.isArray(list)) {
|
|
31
|
+
return new Set()
|
|
32
|
+
}
|
|
33
|
+
return new Set(list.map(String))
|
|
34
|
+
} catch {
|
|
35
|
+
return new Set()
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
16
39
|
/**
|
|
17
40
|
* Перевіряє відповідність проєкту правилам bun.mdc
|
|
18
41
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
@@ -44,6 +67,8 @@ export async function check() {
|
|
|
44
67
|
fail('Відсутній bun.lock — запусти bun i')
|
|
45
68
|
}
|
|
46
69
|
|
|
70
|
+
const cursorRules = await loadNCursorRules()
|
|
71
|
+
|
|
47
72
|
if (existsSync('package.json')) {
|
|
48
73
|
const pkg = JSON.parse(await readFile('package.json', 'utf8'))
|
|
49
74
|
if (pkg.packageManager) {
|
|
@@ -53,6 +78,24 @@ export async function check() {
|
|
|
53
78
|
}
|
|
54
79
|
|
|
55
80
|
const scripts = pkg.scripts && typeof pkg.scripts === 'object' ? pkg.scripts : {}
|
|
81
|
+
|
|
82
|
+
if (cursorRules.has('docker')) {
|
|
83
|
+
if (scripts['lint-docker']) {
|
|
84
|
+
pass('package.json: є `lint-docker` (правило docker у .n-cursor.json)')
|
|
85
|
+
} else {
|
|
86
|
+
fail(
|
|
87
|
+
'У .n-cursor.json є правило `docker` — додай скрипт `lint-docker` у кореневий package.json (див. docker.mdc)'
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (cursorRules.has('k8s')) {
|
|
93
|
+
if (scripts['lint-k8s']) {
|
|
94
|
+
pass('package.json: є `lint-k8s` (правило k8s у .n-cursor.json)')
|
|
95
|
+
} else {
|
|
96
|
+
fail('У .n-cursor.json є правило `k8s` — додай скрипт `lint-k8s` у кореневий package.json (див. k8s.mdc)')
|
|
97
|
+
}
|
|
98
|
+
}
|
|
56
99
|
const lintPrefixed = Object.keys(scripts).filter(name => name.startsWith('lint-'))
|
|
57
100
|
if (lintPrefixed.length > 0) {
|
|
58
101
|
const aggregate = typeof scripts.lint === 'string' ? scripts.lint : ''
|
package/scripts/check-docker.mjs
CHANGED
|
@@ -16,7 +16,7 @@ import { walkDir } from './utils/walkDir.mjs'
|
|
|
16
16
|
* @param {string} name basename шляху
|
|
17
17
|
* @returns {boolean} true для Dockerfile / Dockerfile.* / Containerfile / Containerfile.*
|
|
18
18
|
*/
|
|
19
|
-
function isDockerfileName(name) {
|
|
19
|
+
export function isDockerfileName(name) {
|
|
20
20
|
const n = name.toLowerCase()
|
|
21
21
|
if (n === 'dockerfile' || n === 'containerfile') return true
|
|
22
22
|
if (n.startsWith('dockerfile.') || n.startsWith('containerfile.')) return true
|
|
@@ -28,7 +28,7 @@ function isDockerfileName(name) {
|
|
|
28
28
|
* @param {string} root корінь репозиторію
|
|
29
29
|
* @returns {Promise<string[]>} відсортовані абсолютні шляхи
|
|
30
30
|
*/
|
|
31
|
-
async function findDockerfilePaths(root) {
|
|
31
|
+
export async function findDockerfilePaths(root) {
|
|
32
32
|
/** @type {string[]} */
|
|
33
33
|
const out = []
|
|
34
34
|
await walkDir(root, p => {
|
package/scripts/check-ga.mjs
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Перевіряє GitHub Actions за правилом ga.mdc.
|
|
3
3
|
*
|
|
4
4
|
* Workflows лише з розширенням `.yml`, наявність clean/lint workflow, конфіг zizmor з ref-pin,
|
|
5
|
-
* відсутність MegaLinter, коректний скрипт `lint-ga` у `package.json
|
|
5
|
+
* відсутність MegaLinter, коректний скрипт `lint-ga` у `package.json`, виклик у `lint-ga.yml`,
|
|
6
|
+
* наявність composite `.github/actions/setup-bun-deps/action.yml` (його записує `npx @nitra/cursor`).
|
|
6
7
|
*/
|
|
7
8
|
import { existsSync } from 'node:fs'
|
|
8
9
|
import { readdir, readFile } from 'node:fs/promises'
|
|
@@ -34,6 +35,15 @@ export async function check() {
|
|
|
34
35
|
return exitCode
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
const setupBunDepsAction = '.github/actions/setup-bun-deps/action.yml'
|
|
39
|
+
if (existsSync(setupBunDepsAction)) {
|
|
40
|
+
pass(`${setupBunDepsAction} існує`)
|
|
41
|
+
} else {
|
|
42
|
+
fail(
|
|
43
|
+
`Відсутній ${setupBunDepsAction} — запустіть npx @nitra/cursor або скопіюйте з пакету (ga.mdc: composite setup-bun-deps)`
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
37
47
|
const files = await readdir(wfDir)
|
|
38
48
|
|
|
39
49
|
const yamlFiles = files.filter(f => f.endsWith('.yaml'))
|
package/scripts/check-k8s.mjs
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Перевіряє Kubernetes YAML у шляхах з сегментом `k8s` (див. k8s.mdc).
|
|
3
3
|
*
|
|
4
4
|
* Перший рядок `# yaml-language-server: $schema=…`, без дублікатів, розширення `.yaml`
|
|
5
|
-
* (окрім `kustomization.yml`); URL схеми за першим документом — kustomization / yannh / datree
|
|
5
|
+
* (окрім `kustomization.yml`); URL схеми за першим документом — kustomization / yannh / datree
|
|
6
|
+
* (datree: `https://datreeio.github.io/CRDs-catalog/<group>/<kind>_<version>.json`).
|
|
6
7
|
* Dockerfile — правило docker.mdc, скрипт check-docker.mjs.
|
|
7
8
|
*/
|
|
8
9
|
import { readFile } from 'node:fs/promises'
|
|
@@ -14,14 +15,15 @@ import { walkDir } from './utils/walkDir.mjs'
|
|
|
14
15
|
/** Версія набору схем yannh — узгоджено з k8s.mdc */
|
|
15
16
|
const YANNH_PIN = 'v1.33.9-standalone-strict'
|
|
16
17
|
|
|
17
|
-
/** Гілка репозиторію yannh/kubernetes-json-schema для raw.githubusercontent.com (каталог набору
|
|
18
|
+
/** Гілка репозиторію yannh/kubernetes-json-schema для raw.githubusercontent.com (каталог набору в URL одразу після ref). */
|
|
18
19
|
const YANNH_REF = 'master'
|
|
19
20
|
|
|
20
21
|
const KUSTOMIZATION_SCHEMA = 'https://json.schemastore.org/kustomization.json'
|
|
21
22
|
|
|
22
23
|
const YANNH_BASE = `https://raw.githubusercontent.com/yannh/kubernetes-json-schema/${YANNH_REF}/${YANNH_PIN}/`
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
/** Публікація [CRDs-catalog](https://github.com/datreeio/CRDs-catalog) на GitHub Pages (те саме дерево, що й raw на `main`). */
|
|
26
|
+
const DATREE_CRD_BASE = 'https://datreeio.github.io/CRDs-catalog/'
|
|
25
27
|
|
|
26
28
|
/**
|
|
27
29
|
* Групи API Kubernetes, для яких у перевірці очікується схема yannh (не datree CRD catalog).
|
|
@@ -59,7 +61,7 @@ const MODELINE_RE = /^#\s*yaml-language-server:\s*\$schema=(\S+)\s*$/
|
|
|
59
61
|
* @param {string} filePath шлях до файлу
|
|
60
62
|
* @returns {boolean} true, якщо серед компонентів шляху є каталог `k8s`
|
|
61
63
|
*/
|
|
62
|
-
function pathHasK8sSegment(filePath) {
|
|
64
|
+
export function pathHasK8sSegment(filePath) {
|
|
63
65
|
const parts = filePath.split(/[/\\]/u)
|
|
64
66
|
return parts.includes('k8s')
|
|
65
67
|
}
|
|
@@ -140,7 +142,7 @@ function kindToSchemaFilePart(kind) {
|
|
|
140
142
|
* @param {string} doc перший YAML-документ після modeline
|
|
141
143
|
* @returns {{ expected: string | null, reason: string }} reason — для повідомлень про помилку
|
|
142
144
|
*/
|
|
143
|
-
function expectedSchemaUrl(filePath, doc) {
|
|
145
|
+
export function expectedSchemaUrl(filePath, doc) {
|
|
144
146
|
const base = basename(filePath)
|
|
145
147
|
const baseLower = base.toLowerCase()
|
|
146
148
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Перевіряє структуру npm-модуля в монорепо за правилом npm-module.mdc.
|
|
3
3
|
*
|
|
4
|
-
* Workspace `npm/`, `npm/package.json`, workflow `npm-publish.yml` з OIDC
|
|
4
|
+
* Workspace `npm/`, `npm/package.json`, workflow `npm-publish.yml` з OIDC, `on.push.paths` з glob для каталогу npm (див. npm-module.mdc).
|
|
5
5
|
*/
|
|
6
6
|
import { existsSync } from 'node:fs'
|
|
7
7
|
import { readFile, stat } from 'node:fs/promises'
|
package/scripts/check-text.mjs
CHANGED
|
@@ -4,12 +4,42 @@
|
|
|
4
4
|
* cspell, markdownlint-cli2, скрипт `lint-text` з v8r (`run-v8r.mjs` або чотири `bunx v8r`),
|
|
5
5
|
* `.v8rignore` (vscode JSON),
|
|
6
6
|
* workflow `lint-text.yml`, розширення VSCode для markdownlint.
|
|
7
|
+
*
|
|
8
|
+
* Якщо є `.cursor/rules/n-text.mdc` і/або `npm/mdc/text.mdc` — перевіряє наявність абзацу про український
|
|
9
|
+
* апостроф (U+0027 vs U+2019) і приклад з символом U+2019 у тексті.
|
|
7
10
|
*/
|
|
8
11
|
import { existsSync } from 'node:fs'
|
|
9
12
|
import { readFile } from 'node:fs/promises'
|
|
10
13
|
|
|
11
14
|
import { pass } from './utils/pass.mjs'
|
|
12
15
|
|
|
16
|
+
/** Заголовок абзацу про апостроф у text.mdc / n-text.mdc. */
|
|
17
|
+
const UK_APOSTROPHE_HEADING = '**Український апостроф:**'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Перевіряє абзац про український апостроф у вмісті правила text.
|
|
21
|
+
* @param {string} filePath шлях до файлу (для повідомлень)
|
|
22
|
+
* @param {string} body вміст .mdc у UTF-8
|
|
23
|
+
* @param {(msg: string) => void} failFn реєструє порушення (exit 1)
|
|
24
|
+
* @param {(msg: string) => void} passFn реєструє успішну перевірку
|
|
25
|
+
* @returns {void}
|
|
26
|
+
*/
|
|
27
|
+
function verifyUkApostropheRuleParagraph(filePath, body, failFn, passFn) {
|
|
28
|
+
if (!body.includes(UK_APOSTROPHE_HEADING)) {
|
|
29
|
+
failFn(`${filePath}: додай абзац **Український апостроф:** (U+0027 / U+2019, масив words) — див. text.mdc`)
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
if (!body.includes('U+0027') || !body.includes('U+2019')) {
|
|
33
|
+
failFn(`${filePath}: абзац про апостроф має містити позначки U+0027 та U+2019`)
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
if (!body.includes('\u2019')) {
|
|
37
|
+
failFn(`${filePath}: у прикладі має бути типографський символ U+2019 (\u2019)`)
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
passFn(`${filePath}: абзац про український апостроф на місці`)
|
|
41
|
+
}
|
|
42
|
+
|
|
13
43
|
/**
|
|
14
44
|
* Перевіряє відповідність проєкту правилам text.mdc (cspell, markdownlint-cli2, v8r)
|
|
15
45
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
@@ -104,6 +134,16 @@ export async function check() {
|
|
|
104
134
|
fail('.cspell.json не існує — створи його')
|
|
105
135
|
}
|
|
106
136
|
|
|
137
|
+
const textRulePaths = ['.cursor/rules/n-text.mdc', 'npm/mdc/text.mdc'].filter(p => existsSync(p))
|
|
138
|
+
if (textRulePaths.length === 0) {
|
|
139
|
+
pass('n-text.mdc / npm/mdc/text.mdc відсутні — перевірку абзацу про апостроф пропущено')
|
|
140
|
+
} else {
|
|
141
|
+
for (const p of textRulePaths) {
|
|
142
|
+
const body = await readFile(p, 'utf8')
|
|
143
|
+
verifyUkApostropheRuleParagraph(p, body, fail, pass)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
107
147
|
if (existsSync('package.json')) {
|
|
108
148
|
const pkg = JSON.parse(await readFile('package.json', 'utf8'))
|
|
109
149
|
const devDeps = pkg.devDependencies || {}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Визначення, чи виконується поточний ESM-модуль як точка входу CLI, а не як import у тестах чи інших модулях.
|
|
3
|
+
*
|
|
4
|
+
* У Bun використовується `import.meta.main`; у Node — порівняння `import.meta.url` з `process.argv[1]`
|
|
5
|
+
* після `resolve`, щоб `bun path/to/script.mjs` і `node path/to/script.mjs` коректно вважалися прямим запуском.
|
|
6
|
+
*/
|
|
7
|
+
import { resolve } from 'node:path'
|
|
8
|
+
import { fileURLToPath } from 'node:url'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Чи виконується модуль як точка входу CLI (прямий запуск), а не через import.
|
|
12
|
+
* @returns {boolean} `true`, якщо файл запущено напряму; інакше `false`.
|
|
13
|
+
*/
|
|
14
|
+
export function isRunAsCli() {
|
|
15
|
+
if (import.meta.main === true) {
|
|
16
|
+
return true
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const entry = process.argv[1]
|
|
20
|
+
if (!entry) {
|
|
21
|
+
return false
|
|
22
|
+
}
|
|
23
|
+
return fileURLToPath(import.meta.url) === resolve(entry)
|
|
24
|
+
} catch {
|
|
25
|
+
return false
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Дописує `\@nitra/cursor` у `devDependencies` кореневого `package.json` проєкту, якщо пакет ще не
|
|
3
|
+
* оголошено ні в `devDependencies`, ні в `dependencies`.
|
|
4
|
+
*
|
|
5
|
+
* Використовується CLI `n-cursor` при кожному запуску (`npx \@nitra/cursor`, зокрема `check`), щоб
|
|
6
|
+
* команда `check` і скрипти з `node_modules/\@nitra/cursor/scripts/` були відтворювані після
|
|
7
|
+
* `bun install` / `npm install`, а не лише з кешу npx.
|
|
8
|
+
*
|
|
9
|
+
* Версія діапазону: `^<version>` з поля `version` установленого пакету `\@nitra/cursor`.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { existsSync } from 'node:fs'
|
|
13
|
+
import { readFile, writeFile } from 'node:fs/promises'
|
|
14
|
+
import { dirname, join } from 'node:path'
|
|
15
|
+
import { fileURLToPath } from 'node:url'
|
|
16
|
+
|
|
17
|
+
const PACKAGE_NAME = '@nitra/cursor'
|
|
18
|
+
|
|
19
|
+
const scriptDir = dirname(fileURLToPath(import.meta.url))
|
|
20
|
+
const bundledPkgPath = join(scriptDir, '..', 'package.json')
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Версія з `package.json` пакету `\@nitra/cursor` (каталог на рівень вище за `scripts/`).
|
|
24
|
+
* @returns {Promise<string | null>} поле `version` рядком або `null`, якщо файлу немає / помилка парсингу
|
|
25
|
+
*/
|
|
26
|
+
export async function readBundledPackageVersion() {
|
|
27
|
+
if (!existsSync(bundledPkgPath)) {
|
|
28
|
+
return null
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const raw = await readFile(bundledPkgPath, 'utf8')
|
|
32
|
+
const pkg = JSON.parse(raw)
|
|
33
|
+
return typeof pkg.version === 'string' ? pkg.version : null
|
|
34
|
+
} catch {
|
|
35
|
+
return null
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Якщо в `root/package.json` немає `\@nitra/cursor` у `devDependencies` і `dependencies`, дописує
|
|
41
|
+
* `devDependencies["\@nitra/cursor"]` зі значенням `^<bundledVersion>`.
|
|
42
|
+
* @param {string} root абсолютний шлях кореня проєкту (зазвичай `process.cwd()`)
|
|
43
|
+
* @param {{ bundledVersion?: string | null, silent?: boolean }} [options] `bundledVersion` — для тестів;
|
|
44
|
+
* `silent` — не писати в консоль при успішному оновленні
|
|
45
|
+
* @returns {Promise<boolean>} `true`, якщо `package.json` змінено на диску
|
|
46
|
+
*/
|
|
47
|
+
export async function ensureNitraCursorInRootDevDependencies(root, options = {}) {
|
|
48
|
+
const pkgPath = join(root, 'package.json')
|
|
49
|
+
if (!existsSync(pkgPath)) {
|
|
50
|
+
return false
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let raw
|
|
54
|
+
try {
|
|
55
|
+
raw = await readFile(pkgPath, 'utf8')
|
|
56
|
+
} catch {
|
|
57
|
+
return false
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let pkg
|
|
61
|
+
try {
|
|
62
|
+
pkg = JSON.parse(raw)
|
|
63
|
+
} catch {
|
|
64
|
+
return false
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (pkg === null || typeof pkg !== 'object' || Array.isArray(pkg)) {
|
|
68
|
+
return false
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const devDeps = pkg.devDependencies
|
|
72
|
+
const deps = pkg.dependencies
|
|
73
|
+
if (devDeps && typeof devDeps === 'object' && PACKAGE_NAME in devDeps) {
|
|
74
|
+
return false
|
|
75
|
+
}
|
|
76
|
+
if (deps && typeof deps === 'object' && PACKAGE_NAME in deps) {
|
|
77
|
+
return false
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const ver = options.bundledVersion ?? (await readBundledPackageVersion())
|
|
81
|
+
if (!ver) {
|
|
82
|
+
return false
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!pkg.devDependencies || typeof pkg.devDependencies !== 'object' || Array.isArray(pkg.devDependencies)) {
|
|
86
|
+
pkg.devDependencies = {}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
pkg.devDependencies[PACKAGE_NAME] = `^${ver}`
|
|
90
|
+
|
|
91
|
+
const out = `${JSON.stringify(pkg, null, 2)}\n`
|
|
92
|
+
await writeFile(pkgPath, out, 'utf8')
|
|
93
|
+
|
|
94
|
+
if (!options.silent) {
|
|
95
|
+
console.log(`📝 Додано ${PACKAGE_NAME}@^${ver} у devDependencies у package.json\n`)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return true
|
|
99
|
+
}
|
package/scripts/run-docker.mjs
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { basename } from 'node:path'
|
|
11
11
|
|
|
12
|
+
import { isRunAsCli } from './cli-entry.mjs'
|
|
12
13
|
import { lintDockerfileWithHadolint, posixRel } from './utils/docker-hadolint.mjs'
|
|
13
14
|
import { pass } from './utils/pass.mjs'
|
|
14
15
|
import { walkDir } from './utils/walkDir.mjs'
|
|
@@ -18,7 +19,7 @@ import { walkDir } from './utils/walkDir.mjs'
|
|
|
18
19
|
* @param {string} name basename шляху
|
|
19
20
|
* @returns {boolean} true, якщо ім’я підходить під lint-docker
|
|
20
21
|
*/
|
|
21
|
-
function isLintDockerfileName(name) {
|
|
22
|
+
export function isLintDockerfileName(name) {
|
|
22
23
|
const n = name.toLowerCase()
|
|
23
24
|
if (n === 'dockerfile') return true
|
|
24
25
|
return n.endsWith('.dockerfile') && n.length > '.dockerfile'.length
|
|
@@ -29,7 +30,7 @@ function isLintDockerfileName(name) {
|
|
|
29
30
|
* @param {string} root корінь репозиторію
|
|
30
31
|
* @returns {Promise<string[]>} відсортовані абсолютні шляхи
|
|
31
32
|
*/
|
|
32
|
-
async function findLintDockerfilePaths(root) {
|
|
33
|
+
export async function findLintDockerfilePaths(root) {
|
|
33
34
|
/** @type {string[]} */
|
|
34
35
|
const out = []
|
|
35
36
|
await walkDir(root, p => {
|
|
@@ -73,4 +74,6 @@ async function main() {
|
|
|
73
74
|
return exitCode
|
|
74
75
|
}
|
|
75
76
|
|
|
76
|
-
|
|
77
|
+
if (isRunAsCli()) {
|
|
78
|
+
process.exitCode = await main()
|
|
79
|
+
}
|
package/scripts/run-k8s.mjs
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import { spawnSync } from 'node:child_process'
|
|
16
16
|
import { basename, dirname } from 'node:path'
|
|
17
17
|
|
|
18
|
+
import { isRunAsCli } from './cli-entry.mjs'
|
|
18
19
|
import { walkDir } from './utils/walkDir.mjs'
|
|
19
20
|
|
|
20
21
|
/** Версія Kubernetes для kubeconform — синхронно з YANNH_PIN (без префікса v і суфікса -standalone-strict). */
|
|
@@ -22,14 +23,14 @@ const KUBERNETES_VERSION = '1.33.9'
|
|
|
22
23
|
|
|
23
24
|
/** Додатковий реєстр схем для CRD (як у README kubeconform). */
|
|
24
25
|
const DATREE_CRD_SCHEMA_LOCATION =
|
|
25
|
-
'https://
|
|
26
|
+
'https://datreeio.github.io/CRDs-catalog/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json'
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
29
|
* Чи містить шлях сегмент директорії `k8s`.
|
|
29
30
|
* @param {string} filePath шлях до файлу
|
|
30
31
|
* @returns {boolean} true, якщо серед компонентів шляху є каталог `k8s`
|
|
31
32
|
*/
|
|
32
|
-
function pathHasK8sSegment(filePath) {
|
|
33
|
+
export function pathHasK8sSegment(filePath) {
|
|
33
34
|
const parts = filePath.split(/[/\\]/u)
|
|
34
35
|
return parts.includes('k8s')
|
|
35
36
|
}
|
|
@@ -39,7 +40,7 @@ function pathHasK8sSegment(filePath) {
|
|
|
39
40
|
* @param {string} absFile абсолютний шлях до yaml
|
|
40
41
|
* @returns {string | null} абсолютний шлях до `…/k8s` або null, якщо сегмента `k8s` у ланцюжку немає
|
|
41
42
|
*/
|
|
42
|
-
function k8sRootFromFile(absFile) {
|
|
43
|
+
export function k8sRootFromFile(absFile) {
|
|
43
44
|
let dir = dirname(absFile)
|
|
44
45
|
for (let i = 0; i < 64; i++) {
|
|
45
46
|
if (basename(dir) === 'k8s') return dir
|
|
@@ -55,7 +56,7 @@ function k8sRootFromFile(absFile) {
|
|
|
55
56
|
* @param {string} root корінь репозиторію
|
|
56
57
|
* @returns {Promise<string[]>} відсортовані абсолютні шляхи до каталогів `k8s`
|
|
57
58
|
*/
|
|
58
|
-
async function findK8sRoots(root) {
|
|
59
|
+
export async function findK8sRoots(root) {
|
|
59
60
|
/** @type {Set<string>} */
|
|
60
61
|
const roots = new Set()
|
|
61
62
|
await walkDir(root, p => {
|
|
@@ -136,4 +137,6 @@ async function main() {
|
|
|
136
137
|
return ks
|
|
137
138
|
}
|
|
138
139
|
|
|
139
|
-
|
|
140
|
+
if (isRunAsCli()) {
|
|
141
|
+
process.exitCode = await main()
|
|
142
|
+
}
|
package/scripts/run-v8r.mjs
CHANGED
|
@@ -18,17 +18,36 @@ import { existsSync } from 'node:fs'
|
|
|
18
18
|
import { dirname, join } from 'node:path'
|
|
19
19
|
import { fileURLToPath } from 'node:url'
|
|
20
20
|
|
|
21
|
+
import { isRunAsCli } from './cli-entry.mjs'
|
|
22
|
+
|
|
21
23
|
/** Типові glob-и для форматів, які обробляє v8r (див. опис CLI v8r). */
|
|
22
|
-
const
|
|
24
|
+
export const DEFAULT_V8R_GLOBS = ['**/*.json', '**/*.json5', '**/*.yml', '**/*.yaml', '**/*.toml']
|
|
23
25
|
|
|
24
26
|
/** Абсолютний шлях до `schemas/v8r-catalog.json` поруч з цим скриптом у пакеті `@nitra/cursor`. */
|
|
25
|
-
const V8R_CATALOG_PATH = join(dirname(fileURLToPath(import.meta.url)), '../schemas/v8r-catalog.json')
|
|
27
|
+
export const V8R_CATALOG_PATH = join(dirname(fileURLToPath(import.meta.url)), '../schemas/v8r-catalog.json')
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Повертає шлях до каталогу схем v8r для пакета (для тестів і діагностики).
|
|
31
|
+
* @returns {string} абсолютний шлях до v8r-catalog.json
|
|
32
|
+
*/
|
|
33
|
+
export function getV8rCatalogPath() {
|
|
34
|
+
return V8R_CATALOG_PATH
|
|
35
|
+
}
|
|
26
36
|
|
|
27
|
-
|
|
28
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Запускає послідовні виклики v8r по glob-ам; не змінює process.exitCode (лише повертає код).
|
|
39
|
+
* @param {string[]} [globs] патерни; за замовчуванням DEFAULT_V8R_GLOBS
|
|
40
|
+
* @returns {number} 0 — OK, 1 — помилка spawn, 2 — немає каталогу схем, інше — код v8r
|
|
41
|
+
*/
|
|
42
|
+
export function runV8rWithGlobs(globs = DEFAULT_V8R_GLOBS) {
|
|
43
|
+
if (!existsSync(V8R_CATALOG_PATH)) {
|
|
44
|
+
process.stderr.write(
|
|
45
|
+
`run-v8r: не знайдено каталог схем за шляхом ${V8R_CATALOG_PATH} (очікується npm/schemas/v8r-catalog.json у пакеті)\n`
|
|
46
|
+
)
|
|
47
|
+
return 2
|
|
48
|
+
}
|
|
29
49
|
|
|
30
50
|
for (const pattern of globs) {
|
|
31
|
-
// Порядок важливий: glob має бути перед -c, інакше yargs у v8r не отримує позиційні patterns.
|
|
32
51
|
const result = spawnSync('bun', ['x', 'v8r', pattern, '-c', V8R_CATALOG_PATH], {
|
|
33
52
|
encoding: 'utf8',
|
|
34
53
|
maxBuffer: 50 * 1024 * 1024,
|
|
@@ -38,8 +57,7 @@ if (existsSync(V8R_CATALOG_PATH)) {
|
|
|
38
57
|
|
|
39
58
|
if (result.error) {
|
|
40
59
|
process.stderr.write(`${result.error.message}\n`)
|
|
41
|
-
|
|
42
|
-
break
|
|
60
|
+
return 1
|
|
43
61
|
}
|
|
44
62
|
|
|
45
63
|
const exitCode = result.status ?? 1
|
|
@@ -50,13 +68,13 @@ if (existsSync(V8R_CATALOG_PATH)) {
|
|
|
50
68
|
if (result.stderr?.length) {
|
|
51
69
|
process.stderr.write(result.stderr)
|
|
52
70
|
}
|
|
53
|
-
|
|
54
|
-
break
|
|
71
|
+
return exitCode
|
|
55
72
|
}
|
|
56
73
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
process.
|
|
74
|
+
return 0
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (isRunAsCli()) {
|
|
78
|
+
const globs = process.argv.length > 2 ? process.argv.slice(2) : DEFAULT_V8R_GLOBS
|
|
79
|
+
process.exitCode = runV8rWithGlobs(globs)
|
|
62
80
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Копіює composite GitHub Action `setup-bun-deps` з установленого пакету `@nitra/cursor`
|
|
3
|
+
* у цільовий репозиторій (`.github/actions/setup-bun-deps/action.yml`).
|
|
4
|
+
*
|
|
5
|
+
* Використовується CLI `npx \@nitra/cursor`, щоб workflows з правил `ga` / `js-lint` / `text`
|
|
6
|
+
* могли одразу викликати `uses: ./.github/actions/setup-bun-deps` без ручного копіювання.
|
|
7
|
+
*
|
|
8
|
+
* Джерело: каталог `github-actions/setup-bun-deps/` у корені tarball пакету (поруч із `mdc/`, `bin/`).
|
|
9
|
+
*/
|
|
10
|
+
import { existsSync } from 'node:fs'
|
|
11
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
12
|
+
import { join } from 'node:path'
|
|
13
|
+
|
|
14
|
+
/** Відносний шлях до `action.yml` всередині кореня пакету */
|
|
15
|
+
const RELATIVE_BUNDLED_ACTION = join('github-actions', 'setup-bun-deps', 'action.yml')
|
|
16
|
+
|
|
17
|
+
/** Відносний шлях призначення у проєкті-клієнті */
|
|
18
|
+
const RELATIVE_DEST_ACTION = join('.github', 'actions', 'setup-bun-deps', 'action.yml')
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Записує у `projectRoot` composite action з `bundledPackageRoot` (корінь установленого `@nitra/cursor`).
|
|
22
|
+
* @param {string} projectRoot абсолютний шлях до кореня цільового репозиторію
|
|
23
|
+
* @param {string} bundledPackageRoot абсолютний шлях до кореня пакету (теки з `mdc/`, `github-actions/`)
|
|
24
|
+
* @returns {Promise<{ written: boolean, destPath: string }>} чи був запис і повний шлях файлу
|
|
25
|
+
*/
|
|
26
|
+
export async function syncSetupBunDepsAction(projectRoot, bundledPackageRoot) {
|
|
27
|
+
const srcPath = join(bundledPackageRoot, RELATIVE_BUNDLED_ACTION)
|
|
28
|
+
if (!existsSync(srcPath)) {
|
|
29
|
+
throw new Error(`Не знайдено шаблон composite action.\nОчікуваний шлях: ${srcPath}\nПеревстановіть @nitra/cursor.`)
|
|
30
|
+
}
|
|
31
|
+
const destPath = join(projectRoot, RELATIVE_DEST_ACTION)
|
|
32
|
+
await mkdir(join(projectRoot, '.github', 'actions', 'setup-bun-deps'), { recursive: true })
|
|
33
|
+
const content = await readFile(srcPath, 'utf8')
|
|
34
|
+
await writeFile(destPath, content.endsWith('\n') ? content : `${content}\n`, 'utf8')
|
|
35
|
+
return { written: true, destPath }
|
|
36
|
+
}
|