@nitra/cursor 1.7.1 → 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/bin/n-cursor.js CHANGED
@@ -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,6 +38,12 @@ 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
48
  const CONFIG_FILE = '.n-cursor.json'
37
49
  /** Публічний URL JSON Schema для поля `$schema` у `.n-cursor.json` (IDE); вміст правил CLI читає лише з диска пакету */
@@ -47,6 +59,8 @@ const BUNDLED_MDC_DIR = join(binDir, '..', 'mdc')
47
59
  const BUNDLED_SCRIPTS_DIR = join(binDir, '..', 'scripts')
48
60
  const BUNDLED_SKILLS_DIR = join(binDir, '..', 'skills')
49
61
  const BUNDLED_AGENTS_TEMPLATE_PATH = join(binDir, '..', AGENTS_TEMPLATE_FILE)
62
+ /** Корінь установленого пакету (каталог з `mdc/`, `github-actions/`, …) */
63
+ const BUNDLED_PACKAGE_ROOT = join(binDir, '..')
50
64
 
51
65
  /**
52
66
  * Імена правил (без .mdc) з каталогу mdc поточної інсталяції пакету
@@ -205,24 +219,6 @@ function readBundledRuleContent(rule) {
205
219
  return readFile(bundledPath, 'utf8')
206
220
  }
207
221
 
208
- /**
209
- * Версія з `package.json` установленого пакету (каталог поруч із `bin/`).
210
- * @returns {Promise<string | null>} поле `version` рядком або `null`, якщо файлу немає / помилка парсингу
211
- */
212
- async function readBundledPackageVersion() {
213
- const pkgPath = join(binDir, '..', 'package.json')
214
- if (!existsSync(pkgPath)) {
215
- return null
216
- }
217
- try {
218
- const raw = await readFile(pkgPath, 'utf8')
219
- const pkg = JSON.parse(raw)
220
- return typeof pkg.version === 'string' ? pkg.version : null
221
- } catch {
222
- return null
223
- }
224
- }
225
-
226
222
  /**
227
223
  * Нормалізує id skill з конфігу до форми без префікса n- (як «fix»)
228
224
  * @param {string} skillName елемент масиву skills або ім'я каталогу
@@ -651,6 +647,14 @@ async function runSync() {
651
647
  console.log(`📋 Правил до завантаження: ${rules.length}`)
652
648
  console.log(`📋 Skills до синхронізації: ${skills.length}`)
653
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
+
654
658
  const rulesDir = join(cwd(), RULES_DIR)
655
659
  await mkdir(rulesDir, { recursive: true })
656
660
 
@@ -724,6 +728,7 @@ async function runSync() {
724
728
  const [command, ...args] = process.argv.slice(2)
725
729
 
726
730
  try {
731
+ await ensureNitraCursorInRootDevDependencies(cwd())
727
732
  if (command === 'check') {
728
733
  await runChecks(args)
729
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
@@ -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@v4
72
+ uses: actions/cache@v5
69
73
  with:
70
74
  path: |
71
75
  ~/.bun/install/cache
package/mdc/docker.mdc CHANGED
@@ -45,12 +45,6 @@ on:
45
45
  - '**/Dockerfile'
46
46
  - '**/*.Dockerfile'
47
47
  - '**/*.dockerfile'
48
- - '.hadolint.yaml'
49
- - 'npm/scripts/run-docker.mjs'
50
- - 'npm/scripts/utils/docker-hadolint.mjs'
51
- - 'npm/scripts/check-docker.mjs'
52
- - 'npm/mdc/docker.mdc'
53
- - 'package.json'
54
48
 
55
49
  pull_request:
56
50
  branches:
@@ -66,7 +60,7 @@ jobs:
66
60
  permissions:
67
61
  contents: read
68
62
  steps:
69
- - uses: actions/checkout@v4
63
+ - uses: actions/checkout@v6
70
64
  with:
71
65
  persist-credentials: false
72
66
 
@@ -76,10 +70,14 @@ jobs:
76
70
  chmod +x /tmp/hadolint
77
71
  sudo mv /tmp/hadolint /usr/local/bin/hadolint
78
72
 
73
+ - uses: actions/setup-node@v6
74
+ with:
75
+ node-version: '24'
76
+
79
77
  - uses: oven-sh/setup-bun@v2
80
78
 
81
79
  - name: Cache Bun dependencies
82
- uses: actions/cache@v4
80
+ uses: actions/cache@v5
83
81
  with:
84
82
  path: |
85
83
  ~/.bun/install/cache
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.0.0
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/checkout@v4
103
- with:
104
- persist-credentials: false
99
+ - uses: ./.github/actions/setup-bun-deps
105
100
 
106
- - uses: oven-sh/setup-bun@v2
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/checkout@v4
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"
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.8'
3
+ version: '1.10'
4
4
  globs: "**/k8s/**/*.{yaml,yml}"
5
5
  alwaysApply: false
6
6
  ---
@@ -72,7 +72,7 @@ jobs:
72
72
  permissions:
73
73
  contents: read
74
74
  steps:
75
- - uses: actions/checkout@v4
75
+ - uses: actions/checkout@v6
76
76
  with:
77
77
  persist-credentials: false
78
78
 
@@ -86,10 +86,14 @@ jobs:
86
86
  curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash
87
87
  echo "$HOME/.kubescape/bin" >> $GITHUB_PATH
88
88
 
89
+ - uses: actions/setup-node@v6
90
+ with:
91
+ node-version: '24'
92
+
89
93
  - uses: oven-sh/setup-bun@v2
90
94
 
91
95
  - name: Cache Bun dependencies
92
- uses: actions/cache@v4
96
+ uses: actions/cache@v5
93
97
  with:
94
98
  path: |
95
99
  ~/.bun/install/cache
@@ -117,10 +121,10 @@ jobs:
117
121
 
118
122
  ## Що закодовано в `check-k8s.mjs`
119
123
 
120
- При зміні правил синхронно оновлюй **`YANNH_PIN`**, **`YANNH_REF`** (якщо зміниться гілка за замовчуванням у репо yannh), **`YANNH_GROUPS`**, а в **`run-k8s.mjs`** — константу **`KUBERNETES_VERSION`** (число з PIN, наприклад `v1.33.9-standalone-strict` **`1.33.9`**).
124
+ При зміні правил синхронно оновлюй **`YANNH_PIN`**, **`YANNH_REF`** (якщо зміниться гілка за замовчуванням у репо yannh), **`YANNH_GROUPS`**, **`DATREE_CRD_BASE`** (GitHub Pages каталогу CRD), а в **`run-k8s.mjs`** — константу **`KUBERNETES_VERSION`** і **`DATREE_CRD_SCHEMA_LOCATION`** (узгоджено з базою datree у цьому правилі).
121
125
 
122
126
  - Обхід з пропуском `node_modules`, `.git`, `dist`, `coverage`, `.turbo`, `.next`.
123
- - **`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).
124
128
 
125
129
  ## Коли застосовувати (агентам)
126
130
 
@@ -138,8 +142,22 @@ jobs:
138
142
  3. **`apiVersion: group/version`** і **group** у **`YANNH_GROUPS`** у скрипті → yannh:
139
143
  `https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/<PIN>/<kind>-<group-з-крапками-як-дефіси>-<version>.json`
140
144
  Приклади: `apps/v1` + `Deployment` → `deployment-apps-v1.json`; `networking.k8s.io/v1` + `Ingress` → `ingress-networking-k8s-io-v1.json`.
141
- 4. **Інакше** (CRD тощо) → [datreeio/CRDs-catalog](https://github.com/datreeio/CRDs-catalog):
142
- `https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/CRDs/<group>/<kind>_<version>.json`
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
+
143
161
  5. **Немає надійного публічного URL** — не вигадуй: залиш коректний `$schema` або `file:` за узгодженням.
144
162
 
145
163
  ## Багатодокументні YAML
@@ -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: npm/**`**, **`JS-DevTools/npm-publish@v4`**, **`with.package: npm/package.json`**, **`permissions.id-token: write`** (OIDC на npm).
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@v4
35
+ - uses: actions/checkout@v6
36
36
  with:
37
37
  persist-credentials: false
38
38
 
39
- - uses: actions/setup-node@v5
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
  ```
@@ -76,7 +76,7 @@ jobs:
76
76
  stylelint:
77
77
  runs-on: ubuntu-latest
78
78
  steps:
79
- - uses: actions/checkout@v4
79
+ - uses: actions/checkout@v6
80
80
 
81
81
  - name: StyleLint
82
82
  run: |
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`**, **`markdownlint-cli2`**. Для української додай **`@cspell/dict-uk-ua`**. **`v8r`** лише через **`bun x v8r`** (зазвичай **`bunx v8r`**), не в devDependencies. Окремий пакет **`markdownlint`** не потрібний.
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/checkout@v4
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`** лише для назв/термінів, коли не виправити текстом.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.7.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",
@@ -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` і виклик у `lint-ga.yml`.
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'))
@@ -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'
@@ -21,7 +22,8 @@ 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
- const DATREE_CRD_BASE = 'https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/CRDs/'
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).
@@ -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'
@@ -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
+ }
@@ -23,7 +23,7 @@ const KUBERNETES_VERSION = '1.33.9'
23
23
 
24
24
  /** Додатковий реєстр схем для CRD (як у README kubeconform). */
25
25
  const DATREE_CRD_SCHEMA_LOCATION =
26
- 'https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json'
26
+ 'https://datreeio.github.io/CRDs-catalog/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json'
27
27
 
28
28
  /**
29
29
  * Чи містить шлях сегмент директорії `k8s`.
@@ -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
+ }