@nitra/cursor 1.8.109 → 1.8.110

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/mdc/abie.mdc CHANGED
@@ -4,7 +4,7 @@ alwaysApply: true
4
4
  version: '1.15'
5
5
  ---
6
6
 
7
- Правило **abie** для споживачів **@nitra/cursor**: **k8s** (Deployment + **HealthCheckPolicy** у **`hc.yaml`**, overlay **ua** / **ru** — **nodeSelector**, **HTTPRoute** (будь-який непорожній **`target.name`**, для спільних сервісів **`auth-run-hl`** / **`filelint-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**, а також заборона артефактів **Firebase Hosting** у корені репозиторію.
8
8
 
9
9
  **`npx @nitra/cursor check abie`** виконується лише якщо в **`.n-cursor.json`** у **`rules`** є **`abie`** — інакше вихід **0** без зауважень.
10
10
 
@@ -38,7 +38,7 @@ spec:
38
38
 
39
39
  За наявності **Deployment** під **k8s** і наявності **Vite** (**`vite.config.js`**, **`vite.config.mjs`** або **`vite.config.ts`** у каталозі пакета) у **`ua/kustomization.yaml`** та **`ru/kustomization.yaml`** цього пакета потрібні **inline JSON6902** у **`patches`**: **target** **`kind: HTTPRoute`**, **непорожній `name`** (як у маніфесті маршруту). Мають бути зміни **`/spec/hostnames`** (домени abie — у скрипті) та **`/spec/parentRefs/0/namespace`** (**`ua`** / **`ru`**). Для **ru** — анотація **`gwin.yandex.cloud/rules.http.upgradeTypes: websocket`** лише якщо в **тому ж** **`ru/kustomization.yaml`** є згадка **`HASURA_GRAPHQL_JWT_SECRET`** (типово patch на **ConfigMap** Hasura). Як обирати **`op`** (**add** / **replace** тощо) у patch — **k8s.mdc** (розділ про JSON patch у kustomization).
40
40
 
41
- ### HTTPRoute: спільні сервіси **`auth-run-hl`**, **`filelint-hl`**
41
+ ### HTTPRoute: спільні сервіси **`auth-run-hl`**, **`file-link-hl`**
42
42
 
43
43
  Ці **Service** (headless **`-hl`**) живуть у **базовому** неймспейсі **`dev`**. У маніфесті **HTTPRoute** під **`k8s`** (шар без **`ua/`** та **`ru/`** — наприклад **`…/k8s/base/hr.yaml`**) для кожного **`backendRefs`** до такого сервісу явно вкажи **`namespace: dev`** і порт **8080**:
44
44
 
@@ -53,7 +53,7 @@ spec:
53
53
  - name: auth-run-hl
54
54
  namespace: dev
55
55
  port: 8080
56
- - name: filelint-hl
56
+ - name: file-link-hl
57
57
  namespace: dev
58
58
  port: 8080
59
59
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.8.109",
3
+ "version": "1.8.110",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -29,7 +29,7 @@
29
29
  * — тоді в **`ua`/`ru` kustomization** потрібен patch на **`kind: HTTPRoute`**, **непорожній `target.name`**: **`/spec/hostnames`**
30
30
  * (домени abie.mdc), **`/spec/parentRefs/0/namespace`** (**ua** / **ru**); для **ru** — **`gwin.yandex.cloud/rules.http.upgradeTypes: websocket`**,
31
31
  * якщо в тому ж **`kustomization.yaml`** згадується **`HASURA_GRAPHQL_JWT_SECRET`** (Hasura + JWT).
32
- * **Спільні бекенди (`auth-run-hl`, `filelint-hl`):** у **HTTPRoute** під **`k8s`** поза overlay **ua** та **ru** (шлях не містить **`k8s/ua/`** чи **`k8s/ru/`**) кожен такий **`backendRefs`** має **`namespace: dev`** і порт **8080**;
32
+ * **Спільні бекенди (`auth-run-hl`, `file-link-hl`):** у **HTTPRoute** під **`k8s`** поза overlay **ua** та **ru** (шлях не містить **`k8s/ua/`** чи **`k8s/ru/`**) кожен такий **`backendRefs`** має **`namespace: dev`** і порт **8080**;
33
33
  * у patch overlay **ua** та **ru** — по одному **JSON6902** на **`/spec/rules/…/backendRefs/…/namespace`** з **`value`**: **ua** або **ru** (кількість patch-ів = кількість таких **`backendRefs`** у пакеті).
34
34
  * Вибір **`op`** — **k8s.mdc**.
35
35
  *
@@ -56,7 +56,7 @@ const HASURA_JWT_SECRET_IN_KUSTOMIZATION = 'HASURA_GRAPHQL_JWT_SECRET'
56
56
  * Спільні **Service** (**`-hl`**) у **dev**: у base-**HTTPRoute** обов'язково **`namespace: dev`**, у overlay — patch **`…/backendRefs/…/namespace`** (abie.mdc).
57
57
  * Експорт для споживачів / тестів.
58
58
  */
59
- export const ABIE_SHARED_CROSS_NS_BACKEND_NAMES = Object.freeze(['auth-run-hl', 'filelint-hl'])
59
+ export const ABIE_SHARED_CROSS_NS_BACKEND_NAMES = Object.freeze(['auth-run-hl', 'file-link-hl'])
60
60
 
61
61
  const ABIE_SHARED_CROSS_NS_BACKEND_SET = new Set(ABIE_SHARED_CROSS_NS_BACKEND_NAMES)
62
62
 
@@ -985,7 +985,7 @@ function checkSharedBackendRef(br, rel, errors) {
985
985
  }
986
986
 
987
987
  /**
988
- * З HTTPRoute-документа рахує **`backendRefs`** до **`auth-run-hl`** / **`filelint-hl`** і порушення **`namespace: dev`**.
988
+ * З HTTPRoute-документа рахує **`backendRefs`** до **`auth-run-hl`** / **`file-link-hl`** і порушення **`namespace: dev`**.
989
989
  * @param {unknown} obj корінь YAML
990
990
  * @param {string} rel відносний шлях (повідомлення)
991
991
  * @returns {{ refCount: number, errors: string[] }} кількість посилань і список порушень
@@ -1015,7 +1015,7 @@ function httpRouteDocSharedCrossNsBackendStats(obj, rel) {
1015
1015
  }
1016
1016
 
1017
1017
  /**
1018
- * З YAML під **k8s** пакета (без overlay **ua** та **ru**) збирає кількість **`backendRefs`** до **`auth-run-hl`** і **`filelint-hl`** і порушення **`namespace: dev`**.
1018
+ * З YAML під **k8s** пакета (без overlay **ua** та **ru**) збирає кількість **`backendRefs`** до **`auth-run-hl`** і **`file-link-hl`** і порушення **`namespace: dev`**.
1019
1019
  * @param {string} root корінь репозиторію
1020
1020
  * @param {string} pkgAbs абсолютний шлях до каталогу пакета
1021
1021
  * @param {string[]} yamlFilesAbs усі **yaml** під **k8s** (як **findK8sYamlFiles**)
@@ -1135,7 +1135,7 @@ export function getCombinedNginxRunPatchTextFromKustomization(raw) {
1135
1135
  * @param {string} combined текст одного або кількох inline **patch**, розділених символом нового рядка
1136
1136
  * @param {'ua' | 'ru'} mode **ua** або **ru**
1137
1137
  * @param {string} [fullKustomizationRaw] повний текст **kustomization.yaml** — для **ru** визначає, чи потрібна анотація **gwin…websocket** (лише якщо є **`HASURA_GRAPHQL_JWT_SECRET`**)
1138
- * @param {number} [sharedCrossNsBackendRefCount] скільки **`backendRefs`** до **`auth-run-hl`** і **`filelint-hl`** у base **HTTPRoute** пакета — стільки ж patch-ів **`…/backendRefs/…/namespace`** з **`value`** overlay
1138
+ * @param {number} [sharedCrossNsBackendRefCount] скільки **`backendRefs`** до **`auth-run-hl`** і **`file-link-hl`** у base **HTTPRoute** пакета — стільки ж patch-ів **`…/backendRefs/…/namespace`** з **`value`** overlay
1139
1139
  * @returns {string | null} повідомлення про помилку або **null**
1140
1140
  */
1141
1141
  export function validateAbieNginxRunHttpRoutePatches(
@@ -1173,7 +1173,7 @@ export function validateAbieNginxRunHttpRoutePatches(
1173
1173
  if (sharedCount > 0) {
1174
1174
  const patchHits = countAbieHttpRouteBackendRefNamespacePatchesInCombined(combined, mode)
1175
1175
  if (patchHits < sharedCount) {
1176
- return `HTTPRoute: для backendRefs до спільних сервісів auth-run-hl, filelint-hl очікується ${sharedCount} JSON6902 patch(ів) з path /spec/rules/…/backendRefs/…/namespace та value ${mode} (зараз ${patchHits}) — abie.mdc`
1176
+ return `HTTPRoute: для backendRefs до спільних сервісів auth-run-hl, file-link-hl очікується ${sharedCount} JSON6902 patch(ів) з path /spec/rules/…/backendRefs/…/namespace та value ${mode} (зараз ${patchHits}) — abie.mdc`
1177
1177
  }
1178
1178
  }
1179
1179
  return null
@@ -21,8 +21,9 @@ import { readFile } from 'node:fs/promises'
21
21
  import { createCheckReporter } from './utils/check-reporter.mjs'
22
22
 
23
23
  const OXFMT_END_RE = /&&[ \t]+oxfmt[ \t]+\.[ \t]*$/
24
- const HOISTED_LINKER_RE = /^\s*linker\s*=\s*"hoisted"\s*$/m
25
- const INSTALL_SECTION_RE = /^\s*\[install\]\s*$/m
24
+ /** Пробіли/таби без `\s` (уникаємо super-linear backtracking у sonarjs/slow-regex). */
25
+ const HOISTED_LINKER_RE = /^[ \t]*linker[ \t]*=[ \t]*"hoisted"[ \t]*$/m
26
+ const INSTALL_SECTION_RE = /^[ \t]*\[install\][ \t]*$/m
26
27
 
27
28
  /**
28
29
  * Перевіряє `bunfig.toml` на секцію `[install]` з `linker = "hoisted"`.
@@ -183,6 +183,42 @@ function checkPackageJsonTypeModule(label, pkg, passFn, failFn) {
183
183
  }
184
184
  }
185
185
 
186
+ /**
187
+ * `"type": "module"` у кожного workspace з package.json.
188
+ * @param {unknown[]} workspaces поле workspaces з package.json
189
+ * @param {(msg: string) => void} passFn callback при успішній перевірці
190
+ * @param {(msg: string) => void} failFn callback при помилці
191
+ */
192
+ async function checkWorkspacePackagesTypeModule(workspaces, passFn, failFn) {
193
+ for (const ws of workspaces) {
194
+ const wsPkgPath = `${ws}/package.json`
195
+ if (existsSync(wsPkgPath)) {
196
+ const wsPkg = JSON.parse(await readFile(wsPkgPath, 'utf8'))
197
+ checkPackageJsonTypeModule(wsPkgPath, wsPkg, passFn, failFn)
198
+ }
199
+ }
200
+ }
201
+
202
+ /**
203
+ * engines.node >= 24.
204
+ * @param {{ engines?: { node?: string } }} pkg розпарсений package.json
205
+ * @param {(msg: string) => void} passFn callback при успішній перевірці
206
+ * @param {(msg: string) => void} failFn callback при помилці
207
+ */
208
+ function checkEnginesNode(pkg, passFn, failFn) {
209
+ const nodeEngine = pkg.engines?.node
210
+ if (nodeEngine) {
211
+ const firstNumeric = String(nodeEngine).split(NON_DIGITS_RE).find(Boolean)
212
+ if (firstNumeric && Number(firstNumeric) >= 24) {
213
+ passFn(`engines.node: "${nodeEngine}"`)
214
+ } else {
215
+ failFn(`engines.node: "${nodeEngine}" — має бути >=24`)
216
+ }
217
+ } else {
218
+ failFn('package.json не містить engines.node — додай: "engines": { "node": ">=24" }')
219
+ }
220
+ }
221
+
186
222
  /**
187
223
  * Перевіряє package.json на lint-js, prettier, eslint-config, engines.node.
188
224
  * @param {(msg: string) => void} passFn callback при успішній перевірці
@@ -195,13 +231,7 @@ async function checkPackageJsonJsLint(passFn, failFn) {
195
231
  checkPackageJsonTypeModule('package.json', pkg, passFn, failFn)
196
232
 
197
233
  const workspaces = Array.isArray(pkg.workspaces) ? pkg.workspaces : []
198
- for (const ws of workspaces) {
199
- const wsPkgPath = `${ws}/package.json`
200
- if (existsSync(wsPkgPath)) {
201
- const wsPkg = JSON.parse(await readFile(wsPkgPath, 'utf8'))
202
- checkPackageJsonTypeModule(wsPkgPath, wsPkg, passFn, failFn)
203
- }
204
- }
234
+ await checkWorkspacePackagesTypeModule(workspaces, passFn, failFn)
205
235
 
206
236
  const lintJs = pkg.scripts?.['lint-js']
207
237
  if (lintJs) {
@@ -218,18 +248,7 @@ async function checkPackageJsonJsLint(passFn, failFn) {
218
248
  }
219
249
 
220
250
  checkPackageJsonLintDeps(pkg, passFn, failFn)
221
-
222
- const nodeEngine = pkg.engines?.node
223
- if (nodeEngine) {
224
- const firstNumeric = String(nodeEngine).split(NON_DIGITS_RE).find(Boolean)
225
- if (firstNumeric && Number(firstNumeric) >= 24) {
226
- passFn(`engines.node: "${nodeEngine}"`)
227
- } else {
228
- failFn(`engines.node: "${nodeEngine}" — має бути >=24`)
229
- }
230
- } else {
231
- failFn('package.json не містить engines.node — додай: "engines": { "node": ">=24" }')
232
- }
251
+ checkEnginesNode(pkg, passFn, failFn)
233
252
  }
234
253
 
235
254
  /**
@@ -2336,8 +2336,8 @@ function hasuraRuleIsWebsocket(rule, qlPath) {
2336
2336
  * @returns {{ prefix: string, startIndex: number } | null} виявлений префікс і позиція правила 1 або null
2337
2337
  */
2338
2338
  function findHasuraCanonStart(rules) {
2339
- for (let i = 0; i < rules.length; i++) {
2340
- const r = asPlainRecord(rules[i])
2339
+ for (const [i, rule] of rules.entries()) {
2340
+ const r = asPlainRecord(rule)
2341
2341
  const matches = r === null ? null : r.matches
2342
2342
  if (!Array.isArray(matches) || matches.length !== 1) {
2343
2343
  // наступне правило
@@ -61,7 +61,7 @@ function normalizeSnippet(s) {
61
61
 
62
62
  /**
63
63
  * Перевіряє, чи це виклик `require('<module>')` з рядковим аргументом.
64
- * @param {any} node вузол AST
64
+ * @param {Record<string, unknown> | null | undefined} node вузол AST
65
65
  * @returns {string | null} ім'я модуля з аргументу, інакше `null`
66
66
  */
67
67
  function requireCallModule(node) {
@@ -75,7 +75,7 @@ function requireCallModule(node) {
75
75
 
76
76
  /**
77
77
  * Перевіряє, чи це динамічний `import('<module>')` з рядковим аргументом.
78
- * @param {any} node вузол AST
78
+ * @param {Record<string, unknown> | null | undefined} node вузол AST
79
79
  * @returns {string | null} ім'я модуля, інакше `null`
80
80
  */
81
81
  function dynamicImportModule(node) {
@@ -87,8 +87,8 @@ function dynamicImportModule(node) {
87
87
 
88
88
  /**
89
89
  * Простий рекурсивний обхід AST: заходимо в усі об'єкти/масиви, щоб знайти require/import-вузли.
90
- * @param {any} node корінь або під-вузол AST
91
- * @param {(n: any) => void} visit виклик для кожного об'єкта-вузла
90
+ * @param {unknown} node корінь або під-вузол AST
91
+ * @param {(n: unknown) => void} visit виклик для кожного об'єкта-вузла
92
92
  * @returns {void}
93
93
  */
94
94
  function walkAst(node, visit) {
@@ -101,9 +101,10 @@ function walkAst(node, visit) {
101
101
  visit(node)
102
102
  }
103
103
  for (const key of Object.keys(node)) {
104
- if (key === 'parent') continue
105
- const v = node[key]
106
- if (v && typeof v === 'object') walkAst(v, visit)
104
+ if (key !== 'parent') {
105
+ const v = node[key]
106
+ if (v && typeof v === 'object') walkAst(v, visit)
107
+ }
107
108
  }
108
109
  }
109
110