@nitra/cursor 3.13.0 → 3.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.14.0] - 2026-06-02
4
+
5
+ ### Changed
6
+
7
+ - k8s hasura_configmap: base/dev ConfigMap Hasura-Deployment має містити HASURA_GRAPHQL_ENABLED_APIS="metadata,graphql,pgdump" (точний рядок). Кожен не-base overlay (k8s/<env>/, env≠base/dev), що успадковує Hasura-base, має у kustomization.yaml перевизначати цей ключ до "metadata,graphql" (без pgdump) патчем JSON6902/Strategic Merge на ConfigMap — нова cross-file перевірка validateHasuraOverlayEnabledApisOverride
8
+
3
9
  ## [3.13.0] - 2026-06-02
4
10
 
5
11
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "3.13.0",
3
+ "version": "3.14.0",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -2358,6 +2358,7 @@ export const HASURA_REQUIRED_ENV_KEYS = [
2358
2358
  'HASURA_GRAPHQL_ENABLE_RELAY',
2359
2359
  'HASURA_GRAPHQL_ENABLE_TELEMETRY',
2360
2360
  'HASURA_GRAPHQL_ENABLED_LOG_TYPES',
2361
+ 'HASURA_GRAPHQL_ENABLED_APIS',
2361
2362
  'HASURA_GRAPHQL_DISABLE_EVENTING'
2362
2363
  ]
2363
2364
 
@@ -4929,6 +4930,130 @@ async function validateProdKustomizationOverrides(root, yamlFilesAbs, fail, pass
4929
4930
  }
4930
4931
  }
4931
4932
 
4933
+ /** Очікуване `HASURA_GRAPHQL_ENABLED_APIS` у non-base/dev overlay (без `pgdump` — він лише для base/dev). */
4934
+ const HASURA_OVERLAY_ENABLED_APIS = 'metadata,graphql'
4935
+
4936
+ /** JSON-Pointer ключа `HASURA_GRAPHQL_ENABLED_APIS` у `data` ConfigMap (для JSON6902-патчів). */
4937
+ const HASURA_ENABLED_APIS_DATA_POINTER = '/data/HASURA_GRAPHQL_ENABLED_APIS'
4938
+
4939
+ /**
4940
+ * Чи дерево kustomization (`resources` / `bases` / `components` / `crds`, рекурсивно) містить
4941
+ * **Hasura-Deployment** у шарі base (образ `hasura/graphql-engine`). Маркер того, що overlay успадковує
4942
+ * Hasura-ConfigMap з pgdump-значенням `ENABLED_APIS` і має його перевизначити.
4943
+ * @param {string} kustAbs kustomization.yaml
4944
+ * @param {string} rootNorm нормалізований корінь репо
4945
+ * @returns {Promise<boolean>} true, якщо в успадкованому base є Hasura-Deployment
4946
+ */
4947
+ export async function kustomizationTreeHasHasuraDeployment(kustAbs, rootNorm) {
4948
+ const visited = new Set()
4949
+ const paths = await collectYamlAbsPathsFromKustomizationTree(kustAbs, rootNorm, visited)
4950
+ const rootResolved = resolve(rootNorm)
4951
+ for (const abs of paths) {
4952
+ const rel = (relative(rootResolved, abs) || '').replaceAll('\\', '/')
4953
+ if (!isK8sYamlUnderBaseDirectory(rel)) continue
4954
+ const roots = await readK8sYamlDocumentRootsForInventory(abs)
4955
+ if (roots.some(o => isHasuraDeploymentManifest(o))) return true
4956
+ }
4957
+ return false
4958
+ }
4959
+
4960
+ /**
4961
+ * Значення, яке inline-`patch` присвоює `data.HASURA_GRAPHQL_ENABLED_APIS`. Підтримка двох форматів:
4962
+ * **JSON6902** (`op` add/replace на `/data/HASURA_GRAPHQL_ENABLED_APIS`) і **Strategic Merge**
4963
+ * (`data.HASURA_GRAPHQL_ENABLED_APIS`). Зовнішні patch-файли (`patches[].path`) не охоплені — Plan B trade-off.
4964
+ * @param {string} patchText вміст поля `patch`
4965
+ * @returns {string | null} присвоєне значення (рядок) або null, якщо patch не чіпає цей ключ
4966
+ */
4967
+ export function enabledApisValueFromPatchText(patchText) {
4968
+ const t = typeof patchText === 'string' ? patchText.trim() : ''
4969
+ if (t === '') return null
4970
+ let parsed
4971
+ try {
4972
+ for (const d of parseAllDocuments(t)) {
4973
+ if (d.errors.length === 0) {
4974
+ parsed = d.toJSON()
4975
+ break
4976
+ }
4977
+ }
4978
+ } catch {
4979
+ return null
4980
+ }
4981
+ if (Array.isArray(parsed)) {
4982
+ for (const item of parsed) {
4983
+ if (item === null || typeof item !== 'object' || Array.isArray(item)) continue
4984
+ const rec = /** @type {Record<string, unknown>} */ (item)
4985
+ const op = typeof rec.op === 'string' ? rec.op.trim().toLowerCase() : ''
4986
+ const path = typeof rec.path === 'string' ? normalizeJsonPatchPath(rec.path) : ''
4987
+ if ((op === 'add' || op === 'replace') && path === HASURA_ENABLED_APIS_DATA_POINTER) {
4988
+ return typeof rec.value === 'string' ? rec.value : JSON.stringify(rec.value)
4989
+ }
4990
+ }
4991
+ return null
4992
+ }
4993
+ if (parsed === null || typeof parsed !== 'object') return null
4994
+ const data = /** @type {Record<string, unknown>} */ (parsed).data
4995
+ if (data === null || typeof data !== 'object' || Array.isArray(data)) return null
4996
+ const d = /** @type {Record<string, unknown>} */ (data)
4997
+ if (!Object.hasOwn(d, 'HASURA_GRAPHQL_ENABLED_APIS')) return null
4998
+ const v = d.HASURA_GRAPHQL_ENABLED_APIS
4999
+ return typeof v === 'string' ? v : JSON.stringify(v)
5000
+ }
5001
+
5002
+ /**
5003
+ * Значення, яке `patches[]` kustomization присвоюють `data.HASURA_GRAPHQL_ENABLED_APIS` на цілі **ConfigMap**.
5004
+ * Повертає значення першого patch-а, що чіпає цей ключ, або null, якщо такого немає.
5005
+ * @param {Record<string, unknown>} kust об'єкт kustomization.yaml
5006
+ * @returns {string | null} присвоєне значення або null
5007
+ */
5008
+ export function hasuraEnabledApisOverrideValue(kust) {
5009
+ const patches = kust.patches
5010
+ if (!Array.isArray(patches)) return null
5011
+ for (const p of patches) {
5012
+ if (p === null || typeof p !== 'object' || Array.isArray(p)) continue
5013
+ const pr = /** @type {Record<string, unknown>} */ (p)
5014
+ if (typeof pr.patch !== 'string') continue
5015
+ if (resolvePatchTargetKind(pr) !== 'ConfigMap') continue
5016
+ const v = enabledApisValueFromPatchText(pr.patch)
5017
+ if (v !== null) return v
5018
+ }
5019
+ return null
5020
+ }
5021
+
5022
+ /**
5023
+ * Для кожного **non-base/dev** overlay `kustomization.yaml`, що успадковує Hasura-base (Deployment з
5024
+ * `hasura/graphql-engine`), вимагає у `patches[]` перевизначення **`data.HASURA_GRAPHQL_ENABLED_APIS`**
5025
+ * до **`"metadata,graphql"`** (pgdump лишається строго для base/dev) (k8s.mdc). `kind: Component`
5026
+ * пропускається (env-неутральне джерело, не overlay).
5027
+ * @param {string} root корінь репозиторію
5028
+ * @param {string[]} yamlFilesAbs yaml під k8s
5029
+ * @param {(msg: string) => void} fail callback при помилці
5030
+ * @param {(msg: string) => void} passFn callback при успіху
5031
+ */
5032
+ async function validateHasuraOverlayEnabledApisOverride(root, yamlFilesAbs, fail, passFn) {
5033
+ const rootNorm = resolve(root)
5034
+ const kustFiles = yamlFilesAbs.filter(abs => basename(abs) === 'kustomization.yaml')
5035
+ for (const kustAbs of kustFiles) {
5036
+ const rel = (relative(rootNorm, kustAbs) || kustAbs).replaceAll('\\', '/')
5037
+ const segment = k8sEnvSegmentFromRelPath(rel)
5038
+ if (segment === null || segment === 'base' || segment === 'dev') continue
5039
+ const kust = await readFirstYamlObject(kustAbs)
5040
+ if (kust === null || kust.kind === 'Component') continue
5041
+ if (!(await kustomizationTreeHasHasuraDeployment(kustAbs, rootNorm))) continue
5042
+ const value = hasuraEnabledApisOverrideValue(kust)
5043
+ if (value === HASURA_OVERLAY_ENABLED_APIS) {
5044
+ passFn(`${rel}: overlay '${segment}' перевизначає HASURA_GRAPHQL_ENABLED_APIS="${HASURA_OVERLAY_ENABLED_APIS}" (k8s.mdc)`)
5045
+ } else if (value === null) {
5046
+ fail(
5047
+ `${rel}: overlay '${segment}' має у patches[] перевизначати data.HASURA_GRAPHQL_ENABLED_APIS до "${HASURA_OVERLAY_ENABLED_APIS}" (pgdump лише для base/dev) (k8s.mdc)`
5048
+ )
5049
+ } else {
5050
+ fail(
5051
+ `${rel}: overlay '${segment}' patch data.HASURA_GRAPHQL_ENABLED_APIS має бути "${HASURA_OVERLAY_ENABLED_APIS}" (зараз: ${JSON.stringify(value)}) (k8s.mdc)`
5052
+ )
5053
+ }
5054
+ }
5055
+ }
5056
+
4932
5057
  /**
4933
5058
  * Шукає HPA за `scaleTargetRef.name` серед документів.
4934
5059
  * @param {Record<string, unknown>[]} hpaDocs масив HPA-документів
@@ -6626,5 +6751,7 @@ export async function check(cwd = process.cwd()) {
6626
6751
 
6627
6752
  await validateProdKustomizationOverrides(root, yamlFiles, fail, pass)
6628
6753
 
6754
+ await validateHasuraOverlayEnabledApisOverride(root, yamlFiles, fail, pass)
6755
+
6629
6756
  return reporter.getExitCode()
6630
6757
  }
package/rules/k8s/k8s.mdc CHANGED
@@ -303,6 +303,7 @@ spec:
303
303
  - **`HASURA_GRAPHQL_ENABLE_RELAY`** зі значенням **`"false"`**;
304
304
  - **`HASURA_GRAPHQL_ENABLE_TELEMETRY`** зі значенням **`"false"`**;
305
305
  - **`HASURA_GRAPHQL_ENABLED_LOG_TYPES`** зі значенням **`"startup,http-log"`** (точний рядок);
306
+ - **`HASURA_GRAPHQL_ENABLED_APIS`** зі значенням **`"metadata,graphql,pgdump"`** (точний рядок) — **значення для base/dev**;
306
307
  - **`HASURA_GRAPHQL_DISABLE_EVENTING`** — ключ обов'язковий, значення довільне (за замовчуванням **`"true"`**).
307
308
 
308
309
  Точні умови перевірки — rego-пакет **`k8s.hasura_configmap`** (cross-file прив'язка ConfigMap↔Deployment — у `rules/k8s/js/manifests.mjs`).
@@ -313,9 +314,25 @@ data:
313
314
  HASURA_GRAPHQL_ENABLE_RELAY: 'false'
314
315
  HASURA_GRAPHQL_ENABLE_TELEMETRY: 'false'
315
316
  HASURA_GRAPHQL_ENABLED_LOG_TYPES: 'startup,http-log'
317
+ HASURA_GRAPHQL_ENABLED_APIS: 'metadata,graphql,pgdump'
316
318
  HASURA_GRAPHQL_DISABLE_EVENTING: 'true'
317
319
  ```
318
320
 
321
+ ### `HASURA_GRAPHQL_ENABLED_APIS` поза base/dev
322
+
323
+ `pgdump` дозволено **лише** для **base**/**dev**. Кожен **не-base** overlay (`k8s/<env>/`, де `<env>` ≠ `base`/`dev`), що успадковує Hasura-base, **зобов'язаний** у своєму **`kustomization.yaml`** перевизначити `HASURA_GRAPHQL_ENABLED_APIS` до **`"metadata,graphql"`** (без `pgdump`) — патчем JSON6902 або Strategic Merge на ціль **ConfigMap**. Перевірка — cross-file у `rules/k8s/js/manifests.mjs` (`validateHasuraOverlayEnabledApisOverride`); `kind: Component` пропускається.
324
+
325
+ ```yaml title="k8s/prod/kustomization.yaml"
326
+ patches:
327
+ - target:
328
+ kind: ConfigMap
329
+ name: db-h
330
+ patch: |
331
+ - op: replace
332
+ path: /data/HASURA_GRAPHQL_ENABLED_APIS
333
+ value: metadata,graphql
334
+ ```
335
+
319
336
  ## Kustomize: структура каталогів (`base` / overlays)
320
337
 
321
338
  Трансформуй дерева **`**/k8s`**, щоб **винести спільне** через [Kustomize](https://kustomize.io/): один канонічний **`base`** і тонкі **overlays** для інших середовищ.
@@ -6,6 +6,9 @@
6
6
  # "HASURA_GRAPHQL_ENABLE_RELAY" → "false"
7
7
  # "HASURA_GRAPHQL_ENABLE_TELEMETRY" → "false"
8
8
  # "HASURA_GRAPHQL_ENABLED_LOG_TYPES" → "startup,http-log" (точний рядок)
9
+ # "HASURA_GRAPHQL_ENABLED_APIS" → "metadata,graphql,pgdump" (точний рядок;
10
+ # це значення для base/dev. Не-base overlay-и мають зводити його до "metadata,graphql"
11
+ # патчем у kustomization.yaml — перевіряє JS `validateHasuraOverlayEnabledApisOverride`)
9
12
  # "HASURA_GRAPHQL_DISABLE_EVENTING" → null (ключ обов'язковий,
10
13
  # значення довільне; за замовчуванням рекомендовано "true")
11
14
  #
@@ -39,6 +42,7 @@ required_env := {
39
42
  "HASURA_GRAPHQL_ENABLE_RELAY": "false",
40
43
  "HASURA_GRAPHQL_ENABLE_TELEMETRY": "false",
41
44
  "HASURA_GRAPHQL_ENABLED_LOG_TYPES": "startup,http-log",
45
+ "HASURA_GRAPHQL_ENABLED_APIS": "metadata,graphql,pgdump",
42
46
  "HASURA_GRAPHQL_DISABLE_EVENTING": null,
43
47
  }
44
48