@nitra/cursor 1.8.163 → 1.8.165

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/CHANGELOG.md CHANGED
@@ -4,6 +4,18 @@
4
4
 
5
5
  Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
6
6
 
7
+ ## [1.8.165] - 2026-05-01
8
+
9
+ ### Changed
10
+
11
+ - `ga.mdc` / `check-ga.mjs`: лінт workflow-ів через [`github-actionlint`](https://www.npmjs.com/package/github-actionlint) замість `node-actionlint`. Канонічний скрипт `lint-ga` тепер `bunx github-actionlint && uvx zizmor --offline --collect=workflows .`; `check-ga` вимагає у `package.json` саме `github-actionlint`.
12
+
13
+ ## [1.8.164] - 2026-05-01
14
+
15
+ ### Added
16
+
17
+ - `abie.mdc` (v1.16) / `check-abie.mjs`: нова перевірка `.github/actionlint.yaml`. Якщо файл відсутній — `npx @nitra/cursor check abie` створює його з канонічним вмістом (`self-hosted-runner.labels: ['ua', 'dev', 'ru']`); якщо є — звіряє, що в `self-hosted-runner.labels` присутні мітки `ua`, `dev`, `ru` (порядок, інші мітки й формат лапок дозволені). Експортовано `ABIE_REQUIRED_ACTIONLINT_LABELS`, `parseActionlintSelfHostedLabels`, `abieMissingActionlintLabels`.
18
+
7
19
  ## [1.8.163] - 2026-05-01
8
20
 
9
21
  ### Changed
package/mdc/abie.mdc CHANGED
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  description: Правила для проєктів AbInBev Efes
3
3
  alwaysApply: true
4
- version: '1.15'
4
+ version: '1.16'
5
5
  ---
6
6
 
7
- Правило **abie** для споживачів **@nitra/cursor**: **k8s** (Deployment + **HealthCheckPolicy** у **`hc.yaml`**, overlay **ua** / **ru** — **nodeSelector**, **HTTPRoute** (будь-який непорожній **`target.name`**, для спільних сервісів **`auth-run-hl`** / **`file-link-hl`** — **`namespace: dev`** у base та patch **`…/backendRefs/…/namespace`** у **ua** / **ru**), у overlay **ru** — кожен **Service** (у т. ч. **headless** / **`-hl`**) → **`spec.type: NodePort`** через **JSON6902** у **`kustomization.yaml`**, видалення **HealthCheckPolicy** у **ru**), гілки **dev**, **ua**, **ru** у **clean-merged-branch**, а також заборона тримати артефакти **Firebase Hosting** у **підкаталогах першого рівня** (безпосередні діти кореня репозиторію; у самому корені ці імена не вимагаються до видалення).
7
+ Правило **abie** для споживачів **@nitra/cursor**: **k8s** (Deployment + **HealthCheckPolicy** у **`hc.yaml`**, overlay **ua** / **ru** — **nodeSelector**, **HTTPRoute** (будь-який непорожній **`target.name`**, для спільних сервісів **`auth-run-hl`** / **`file-link-hl`** — **`namespace: dev`** у base та patch **`…/backendRefs/…/namespace`** у **ua** / **ru**), у overlay **ru** — кожен **Service** (у т. ч. **headless** / **`-hl`**) → **`spec.type: NodePort`** через **JSON6902** у **`kustomization.yaml`**, видалення **HealthCheckPolicy** у **ru**), гілки **dev**, **ua**, **ru** у **clean-merged-branch**, **`.github/actionlint.yaml`** із мітками **`self-hosted-runner`** **ua** / **dev** / **ru** (створюється автоматично за відсутності), а також заборона тримати артефакти **Firebase Hosting** у **підкаталогах першого рівня** (безпосередні діти кореня репозиторію; у самому корені ці імена не вимагаються до видалення).
8
8
 
9
9
  **`npx @nitra/cursor check abie`** виконується лише якщо в **`.n-cursor.json`** у **`rules`** є **`abie`** — інакше вихід **0** без зауважень.
10
10
 
@@ -336,6 +336,18 @@ spec:
336
336
 
337
337
  У **кожному** підкаталозі, що лежить **безпосередньо** в корені репозиторію, не тримати конфіг і кеш **Firebase Hosting**: у таких каталогах не повинно бути **`.firebaserc`**, **`firebase.json`** та каталогу **`.firebase/`** (у **самому** корені репозиторію ці імена перевіркою abie **не** розглядаються; `node_modules` / `.git` зі скану вилучаються).
338
338
 
339
+ ## actionlint: self-hosted-runner labels
340
+
341
+ У **`.github/actionlint.yaml`** має бути блок **`self-hosted-runner.labels`** з присутніми мітками **`ua`**, **`dev`**, **`ru`**. Якщо файлу немає — **`npx @nitra/cursor check abie`** створює його з канонічним вмістом. Інші мітки, інший порядок та формат лапок дозволені — перевіряється лише наявність трьох обов'язкових міток (деталі — **`check-abie.mjs`**).
342
+
343
+ ```yaml title=".github/actionlint.yaml"
344
+ self-hosted-runner:
345
+ labels:
346
+ - 'ua'
347
+ - 'dev'
348
+ - 'ru'
349
+ ```
350
+
339
351
  ## Git branches
340
352
 
341
353
  У **`.github/workflows/clean-merged-branch.yml`** у кроці **`phpdocker-io/github-actions-delete-abandoned-branches`** значення **`with.ignore_branches`** має містити **dev**, **ua** та **ru** (разом з іншими гілками, якщо потрібно):
package/mdc/ga.mdc CHANGED
@@ -231,11 +231,11 @@ jobs:
231
231
  - uses: ./.github/actions/setup-bun-deps
232
232
  ```
233
233
 
234
- **Лінт:** [actionlint](https://github.com/rhysd/actionlint) через [node-actionlint](https://www.npmjs.com/package/node-actionlint); [zizmor](https://docs.zizmor.sh) — `uvx`, офлайн. Скрипт у корені:
234
+ **Лінт:** [actionlint](https://github.com/rhysd/actionlint) через [github-actionlint](https://www.npmjs.com/package/github-actionlint); [zizmor](https://docs.zizmor.sh) — `uvx`, офлайн. Скрипт у корені:
235
235
 
236
236
  ```json title="package.json"
237
237
  "scripts": {
238
- "lint-ga": "bunx node-actionlint && uvx zizmor --offline --collect=workflows ."
238
+ "lint-ga": "bunx github-actionlint && uvx zizmor --offline --collect=workflows ."
239
239
  }
240
240
  ```
241
241
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.8.163",
3
+ "version": "1.8.165",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -49,6 +49,6 @@
49
49
  "node": ">=25"
50
50
  },
51
51
  "devDependencies": {
52
- "@nitra/cursor": "^1.8.163"
52
+ "@nitra/cursor": "^1.8.165"
53
53
  }
54
54
  }
@@ -39,7 +39,7 @@
39
39
  * у файлі **`k8s/ru/kustomization.yaml`** того ж пакета (overlay середовища **ru**) — inline **JSON6902** на **`kind: Service`** з тим самим **`target.name`**: **`path: /spec/type`**, **`value: NodePort`**; якщо в base було **`spec.clusterIP: None`** — **`op: remove`** для **`/spec/clusterIP`**; якщо в base **явно** задано **`spec.clusterIPs`** — також **`remove`** для **`/spec/clusterIPs`** (інакше **API** може залишити **`None`** для **NodePort**; без ключа **`clusterIPs`** у base **`remove`** на **`/spec/clusterIPs`** ламає **`kubectl kustomize`**).
40
40
  */
41
41
  import { existsSync } from 'node:fs'
42
- import { readdir, readFile } from 'node:fs/promises'
42
+ import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises'
43
43
  import { dirname, join, relative } from 'node:path'
44
44
 
45
45
  import { parseAllDocuments } from 'yaml'
@@ -118,6 +118,20 @@ const HTTPROUTE_BACKENDREF_PORT_8081_VALUE_FIRST_RE =
118
118
  /** Гілки, які мають бути в **`ignore_branches`** за abie.mdc. */
119
119
  export const ABIE_REQUIRED_IGNORE_BRANCHES = ['dev', 'ua', 'ru']
120
120
 
121
+ /** Канонічний шлях до конфігу actionlint у репо (abie.mdc). */
122
+ const ABIE_ACTIONLINT_PATH = '.github/actionlint.yaml'
123
+
124
+ /** Канонічний вміст **`.github/actionlint.yaml`**, який ми створюємо за відсутності файлу (abie.mdc). */
125
+ const ABIE_ACTIONLINT_TEMPLATE = `self-hosted-runner:
126
+ labels:
127
+ - 'ua'
128
+ - 'dev'
129
+ - 'ru'
130
+ `
131
+
132
+ /** Мітки **`self-hosted-runner.labels`**, які мають бути присутні в **`.github/actionlint.yaml`** (abie.mdc). */
133
+ export const ABIE_REQUIRED_ACTIONLINT_LABELS = Object.freeze(['ua', 'dev', 'ru'])
134
+
121
135
  /**
122
136
  * Чи відносний шлях вказує на **`ru/kustomization.yaml`** (сегмент **`ru`** перед ім'ям файлу) — специфіка abie overlay.
123
137
  * @param {string} rel шлях від кореня репозиторію
@@ -1717,9 +1731,82 @@ async function ensureNoFirebaseHostingArtifacts(root, passFn, failFn) {
1717
1731
  }
1718
1732
 
1719
1733
  /**
1720
- * Перевіряє відповідність проєкту правилам abie.mdc.
1721
- * @returns {Promise<number>} 0 OK, 1 — є порушення
1734
+ * Витягує мітки **`self-hosted-runner.labels`** з тексту `.github/actionlint.yaml`.
1735
+ * @param {string} raw повний вміст файлу (YAML)
1736
+ * @returns {string[] | null} масив рядків-міток або null, якщо ключа/масиву не знайдено
1737
+ */
1738
+ export function parseActionlintSelfHostedLabels(raw) {
1739
+ let docs
1740
+ try {
1741
+ docs = parseAllDocuments(raw)
1742
+ } catch {
1743
+ return null
1744
+ }
1745
+ for (const doc of docs) {
1746
+ if (doc.errors.length > 0) continue
1747
+ const root = doc.toJSON()
1748
+ if (root === null || typeof root !== 'object' || Array.isArray(root)) continue
1749
+ const block = /** @type {Record<string, unknown>} */ (root)['self-hosted-runner']
1750
+ if (block === null || typeof block !== 'object' || Array.isArray(block)) continue
1751
+ const labels = /** @type {Record<string, unknown>} */ (block).labels
1752
+ if (!Array.isArray(labels)) continue
1753
+ return labels.filter(l => typeof l === 'string')
1754
+ }
1755
+ return null
1756
+ }
1757
+
1758
+ /**
1759
+ * Які з **`ABIE_REQUIRED_ACTIONLINT_LABELS`** відсутні в наданому списку міток (abie.mdc).
1760
+ * @param {string[]} labels мітки **`self-hosted-runner.labels`**
1761
+ * @returns {string[]} відсутні мітки (порожньо — все ок)
1722
1762
  */
1763
+ export function abieMissingActionlintLabels(labels) {
1764
+ const present = new Set(labels.map(l => l.trim()))
1765
+ return ABIE_REQUIRED_ACTIONLINT_LABELS.filter(r => !present.has(r))
1766
+ }
1767
+
1768
+ /**
1769
+ * Гарантує наявність **`.github/actionlint.yaml`** із потрібними мітками **`self-hosted-runner`** (abie.mdc):
1770
+ * створює файл із канонічним вмістом, якщо його немає; якщо є — звіряє мітки.
1771
+ * @param {string} root корінь репозиторію
1772
+ * @param {(msg: string) => void} pass callback при успішній перевірці
1773
+ * @param {(msg: string) => void} fail callback при помилці
1774
+ */
1775
+ async function ensureAbieActionlintConfig(root, pass, fail) {
1776
+ const abs = join(root, ABIE_ACTIONLINT_PATH)
1777
+ if (!existsSync(abs)) {
1778
+ try {
1779
+ await mkdir(dirname(abs), { recursive: true })
1780
+ await writeFile(abs, ABIE_ACTIONLINT_TEMPLATE, 'utf8')
1781
+ } catch (error) {
1782
+ const msg = error instanceof Error ? error.message : String(error)
1783
+ fail(`${ABIE_ACTIONLINT_PATH}: не вдалося створити (${msg}) — abie.mdc`)
1784
+ return
1785
+ }
1786
+ pass(`${ABIE_ACTIONLINT_PATH}: створено з self-hosted-runner.labels [ua, dev, ru] (abie.mdc)`)
1787
+ return
1788
+ }
1789
+ let raw
1790
+ try {
1791
+ raw = await readFile(abs, 'utf8')
1792
+ } catch (error) {
1793
+ const msg = error instanceof Error ? error.message : String(error)
1794
+ fail(`${ABIE_ACTIONLINT_PATH}: не вдалося прочитати (${msg}) — abie.mdc`)
1795
+ return
1796
+ }
1797
+ const labels = parseActionlintSelfHostedLabels(raw)
1798
+ if (labels === null) {
1799
+ fail(`${ABIE_ACTIONLINT_PATH}: не знайдено self-hosted-runner.labels — додай мітки ua, dev, ru (abie.mdc)`)
1800
+ return
1801
+ }
1802
+ const missing = abieMissingActionlintLabels(labels)
1803
+ if (missing.length > 0) {
1804
+ fail(`${ABIE_ACTIONLINT_PATH}: у self-hosted-runner.labels бракує ${missing.join(', ')} (abie.mdc)`)
1805
+ return
1806
+ }
1807
+ pass(`${ABIE_ACTIONLINT_PATH}: self-hosted-runner.labels містить ua, dev, ru (abie.mdc)`)
1808
+ }
1809
+
1723
1810
  /**
1724
1811
  * Перевіряє clean-merged-branch.yml на ignore_branches.
1725
1812
  * @param {string} root корінь репозиторію
@@ -2091,6 +2178,7 @@ export async function check() {
2091
2178
  pass('Правило abie увімкнено — виконуємо перевірки')
2092
2179
  await ensureNoFirebaseHostingArtifacts(root, pass, fail)
2093
2180
  await checkCleanMergedBranch(root, pass, fail)
2181
+ await ensureAbieActionlintConfig(root, pass, fail)
2094
2182
 
2095
2183
  const yamlFiles = await findK8sYamlFiles(root)
2096
2184
  const deploymentDirs = await collectDeploymentDirs(root, yamlFiles, fail)
@@ -738,10 +738,10 @@ async function checkLintGaScript(passFn, failFn) {
738
738
  return
739
739
  }
740
740
  passFn('package.json містить lint-ga')
741
- if (lg.includes('node-actionlint')) {
742
- passFn('lint-ga викликає node-actionlint')
741
+ if (lg.includes('github-actionlint')) {
742
+ passFn('lint-ga викликає github-actionlint')
743
743
  } else {
744
- failFn('lint-ga має містити bunx node-actionlint (ga.mdc)')
744
+ failFn('lint-ga має містити bunx github-actionlint (ga.mdc)')
745
745
  }
746
746
  if (lg.includes('zizmor') && lg.includes('--offline')) {
747
747
  passFn('lint-ga викликає zizmor з --offline')