@nitra/cursor 1.8.222 → 1.9.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.
Files changed (33) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/bin/n-cursor.js +3 -2
  3. package/mdc/abie.mdc +13 -0
  4. package/mdc/changelog.mdc +3 -2
  5. package/mdc/ci4.mdc +8 -0
  6. package/mdc/ga.mdc +3 -2
  7. package/mdc/graphql.mdc +3 -2
  8. package/mdc/hasura.mdc +3 -2
  9. package/mdc/image-avif.mdc +3 -2
  10. package/mdc/image-compress.mdc +3 -2
  11. package/mdc/k8s.mdc +1 -3
  12. package/mdc/nginx-default-tpl.mdc +3 -1
  13. package/mdc/php.mdc +3 -2
  14. package/mdc/style-lint.mdc +3 -2
  15. package/mdc/vue.mdc +3 -2
  16. package/package.json +1 -1
  17. package/policy/abie/base_deployment_preem/base_deployment_preem.rego +56 -0
  18. package/policy/abie/base_deployment_preem/base_deployment_preem_test.rego +60 -0
  19. package/policy/abie/clean_merged_ignore_branches/clean_merged_ignore_branches.rego +100 -0
  20. package/policy/abie/clean_merged_ignore_branches/clean_merged_ignore_branches_test.rego +48 -0
  21. package/policy/abie/health_check_policy/health_check_policy.rego +91 -22
  22. package/policy/abie/health_check_policy/health_check_policy_test.rego +99 -0
  23. package/policy/abie/http_route_base/http_route_base_test.rego +64 -0
  24. package/policy/k8s/kustomization/kustomization.rego +2 -2
  25. package/policy/k8s/manifest/manifest.rego +4 -2
  26. package/scripts/check-abie.mjs +102 -369
  27. package/scripts/check-ga.mjs +89 -9
  28. package/scripts/check-k8s.mjs +129 -704
  29. package/scripts/lint-conftest.mjs +25 -2
  30. package/scripts/lint-ga.mjs +18 -132
  31. package/scripts/utils/run-conftest-batch.mjs +117 -0
  32. package/policy/k8s/kustomize_managed/kustomize_managed.rego +0 -31
  33. package/policy/k8s/kustomize_managed/kustomize_managed_test.rego +0 -30
@@ -79,6 +79,8 @@ const K8S_DIR_PATH_RE = /(^|\/)k8s\//u
79
79
  const K8S_HC_YAML_PATH_RE = /(^|\/)k8s\/.+\/hc\.yaml$/u
80
80
  /** `…/k8s/…/base/…/hr.yaml` (HTTPRoute у base-шарі). */
81
81
  const K8S_BASE_HR_YAML_PATH_RE = /(^|\/)k8s\/.*base\/.*hr\.yaml$/u
82
+ /** Будь-який ресурсний YAML під `…/k8s/.../base/...` (для abie.base_deployment_preem). */
83
+ const K8S_BASE_RESOURCE_PATH_RE = /(^|\/)k8s\/.*base\//u
82
84
  /** `kustomization.yaml` будь-де під сегментом `k8s/`. */
83
85
  const K8S_KUSTOMIZATION_PATH_RE = /(^|\/)k8s\/.*\/kustomization\.yaml$/u
84
86
  /** `…/k8s/.../base/.../kustomization.yaml`. */
@@ -301,7 +303,7 @@ const TARGETS = [
301
303
  // abie HealthCheckPolicy: `hc.yaml` у дереві k8s.
302
304
  {
303
305
  namespace: 'abie.health_check_policy',
304
- policyDir: 'abie',
306
+ policyDir: 'abie/health_check_policy',
305
307
  rule: 'abie',
306
308
  walk: { match: rel => K8S_HC_YAML_PATH_RE.test(rel) }
307
309
  },
@@ -309,9 +311,30 @@ const TARGETS = [
309
311
  // abie HTTPRoute у `base/`.
310
312
  {
311
313
  namespace: 'abie.http_route_base',
312
- policyDir: 'abie',
314
+ policyDir: 'abie/http_route_base',
313
315
  rule: 'abie',
314
316
  walk: { match: rel => K8S_BASE_HR_YAML_PATH_RE.test(rel) }
317
+ },
318
+
319
+ // abie Deployment у `…/k8s/.../base/...` має preem nodeSelector.
320
+ {
321
+ namespace: 'abie.base_deployment_preem',
322
+ policyDir: 'abie/base_deployment_preem',
323
+ rule: 'abie',
324
+ walk: {
325
+ match: rel =>
326
+ K8S_BASE_RESOURCE_PATH_RE.test(rel) &&
327
+ !K8S_BASE_KUSTOMIZATION_PATH_RE.test(rel) &&
328
+ (rel.endsWith('.yaml') || rel.endsWith('.yml'))
329
+ }
330
+ },
331
+
332
+ // abie clean-merged-branch.yml: with.ignore_branches має містити dev/ua/ru.
333
+ {
334
+ namespace: 'abie.clean_merged_ignore_branches',
335
+ policyDir: 'abie/clean_merged_ignore_branches',
336
+ rule: 'abie',
337
+ single: '.github/workflows/clean-merged-branch.yml'
315
338
  }
316
339
  ]
317
340
 
@@ -1,18 +1,14 @@
1
1
  /**
2
2
  * CLI-обгортка над канонічним `lint-ga` (ga.mdc): робить preflight на `shellcheck` і `uv` (для `uvx`),
3
3
  * тоді послідовно виконує `bunx github-actionlint`, `uvx zizmor --offline --collect=workflows .` і
4
- * (PoC) `conftest test` на структуру канонічних workflow проти Rego-полісі з `npm/policy/ga/`.
4
+ * делегує до `check-ga.mjs::check()` там і Rego-частина (через `runConftestBatch`),
5
+ * і JS cross-file перевірки правил `ga.mdc`.
5
6
  *
6
- * Conftest-крок навмисно **не** додається в preflight: якщо бінарник не встановлений, виводимо `ℹ`
7
- * повідомлення й продовжуємо з кодом 0. Структурні перевірки тих самих workflow паралельно живуть у
8
- * `npm/scripts/check-ga.mjs`, тож відсутність conftest не пропускає порушення мовчки.
9
- *
10
- * Conftest проганяється у двох режимах:
11
- * 1) per-workflow polysi (`ga.<name>`) — для канонічних `clean-ga-workflows`, `clean-merged-branch`,
12
- * `lint-ga`, `git-ai`, що мають фіксовані поля (cron, ім'я кроку тощо);
13
- * 2) `ga.workflow_common` — універсальні правила (concurrency, заборонені setup-bun/cache/install у
14
- * кроках, shell line-continuation у `run:`, checkout перед локальним setup-bun-deps), які
15
- * застосовуються до **кожного** `.github/workflows/*.yml`.
7
+ * Plan B-патерн (rego-authoritative): Rego-полісі (`npm/policy/ga/`) запускає вже сам
8
+ * `check-ga.mjs::check()` як перший крок `lint-ga.mjs` про це не знає. Раніше `lint-ga.mjs` сам
9
+ * спавнив conftest для `ga.<name>` per-workflow і `ga.workflow_common` (PoC); тепер ця логіка
10
+ * централізована у `check-ga.mjs`, тож одне джерело істини, без дублювання між
11
+ * `lint-ga` і `npx @nitra/cursor check ga`.
16
12
  *
17
13
  * Без preflight `actionlint` (через `bunx github-actionlint`) мовчки пропускає shell-перевірки в
18
14
  * `run:` блоках, коли `shellcheck` відсутній у PATH; локально `bun lint-ga` лишається зеленим, а CI
@@ -23,49 +19,12 @@
23
19
  *
24
20
  * Експортовано окремо `runLintGaCli` — використовується з `bin/n-cursor.js` як підкоманда `lint-ga`.
25
21
  */
26
- import { existsSync, readdirSync } from 'node:fs'
27
22
  import { spawnSync } from 'node:child_process'
28
- import { dirname, join } from 'node:path'
29
23
  import { platform } from 'node:process'
30
- import { fileURLToPath } from 'node:url'
31
24
 
25
+ import { check as checkGa } from './check-ga.mjs'
32
26
  import { resolveCmd } from './utils/resolve-cmd.mjs'
33
27
 
34
- /** Каталог пакету `@nitra/cursor`, від якого ресолвимо вшиту директорію policy/. */
35
- const PACKAGE_ROOT = dirname(dirname(fileURLToPath(import.meta.url)))
36
-
37
- /** Шлях до кореня Rego-полісі для GA. У npm-tarball публікується через `files: ["policy"]` у package.json. */
38
- const GA_POLICY_DIR = join(PACKAGE_ROOT, 'policy', 'ga')
39
-
40
- /**
41
- * Workflow-файли, для яких маємо відповідну Rego-полісі. Кожен таргет посилається на під-пакет
42
- * `ga.<name>` у `policy/ga/<name>/<name>.rego`; conftest викликаємо з `--namespace`, щоб правила
43
- * іншого workflow не застосовувалися до чужого файлу.
44
- * @type {Array<{ workflow: string, namespace: string, label: string }>}
45
- */
46
- const CONFTEST_TARGETS = [
47
- {
48
- workflow: '.github/workflows/clean-ga-workflows.yml',
49
- namespace: 'ga.clean_ga_workflows',
50
- label: 'clean-ga-workflows.yml structure'
51
- },
52
- {
53
- workflow: '.github/workflows/clean-merged-branch.yml',
54
- namespace: 'ga.clean_merged_branch',
55
- label: 'clean-merged-branch.yml structure'
56
- },
57
- {
58
- workflow: '.github/workflows/lint-ga.yml',
59
- namespace: 'ga.lint_ga',
60
- label: 'lint-ga.yml structure'
61
- },
62
- {
63
- workflow: '.github/workflows/git-ai.yml',
64
- namespace: 'ga.git_ai',
65
- label: 'git-ai.yml structure'
66
- }
67
- ]
68
-
69
28
  /**
70
29
  * Опис залежності preflight-ом: бінарник, для чого потрібен, і команди встановлення.
71
30
  * @typedef {object} PreflightDep
@@ -177,22 +136,24 @@ function runStep(title, cmd, args) {
177
136
  }
178
137
 
179
138
  /**
180
- * Виконує канонічний `lint-ga` з preflight-перевірками й послідовним запуском actionlint + zizmor.
139
+ * Виконує канонічний `lint-ga` з preflight-перевірками і делегує до `check-ga.check()`.
181
140
  *
182
141
  * Послідовність:
183
142
  * 1) preflight: `shellcheck` (для actionlint SC-правил) і `uv` (для `uvx zizmor`); відсутній → exit 1;
184
143
  * 2) `bunx github-actionlint`;
185
- * 3) `uvx zizmor --offline --collect=workflows .`.
144
+ * 3) `uvx zizmor --offline --collect=workflows .`;
145
+ * 4) `check-ga.mjs::check()` — Rego-полісі (батч conftest з `npm/policy/ga/`) + JS cross-file
146
+ * перевірки правил `ga.mdc`. Це **те саме**, що робить `npx @nitra/cursor check ga`, тож
147
+ * `lint-ga` тепер є суперсетом перевірки правила: external-tools + check.
186
148
  *
187
149
  * Якщо хоча б один preflight не пройшов — виходимо одразу з кодом 1, **до** запуску actionlint/zizmor,
188
150
  * бо їхні власні повідомлення про відсутність залежностей менш інформативні (особливо для shellcheck —
189
151
  * actionlint мовчки пропускає SC-правила; ця перевірка — головний сенс обгортки).
190
152
  *
191
- * Першу помилку від actionlint/zizmor повертаємо як код виходу; наступні кроки не запускаються
192
- * (відповідає `&&` у package.json).
193
- * @returns {number} 0 — все OK, інакше — код першого кроку, що впав
153
+ * Першу помилку від actionlint/zizmor/check повертаємо як код виходу; наступні кроки не запускаються.
154
+ * @returns {Promise<number>} 0 — все OK, інакше — код першого кроку, що впав
194
155
  */
195
- export function runLintGaCli() {
156
+ export async function runLintGaCli() {
196
157
  let preflightOk = true
197
158
  for (const dep of [SHELLCHECK_PREFLIGHT, UV_PREFLIGHT]) {
198
159
  if (!preflight(dep)) preflightOk = false
@@ -205,81 +166,6 @@ export function runLintGaCli() {
205
166
  const zizmorCode = runStep('zizmor', 'uvx', ['zizmor', '--offline', '--collect=workflows', '.'])
206
167
  if (zizmorCode !== 0) return zizmorCode
207
168
 
208
- return runConftestStep()
209
- }
210
-
211
- /**
212
- * PoC-крок: запускає conftest на YAML workflow проти Rego-полісі з пакету (`policy/ga/`).
213
- *
214
- * Поведінка fallback:
215
- * - якщо `conftest` не знайдено в PATH — друкуємо `ℹ` повідомлення з підказкою встановлення й
216
- * повертаємо 0 (тобто конфтест поки що **не** є обовʼязковою залежністю lint-ga; перевірки лежать
217
- * паралельно в `check-ga.mjs`, і `npx \@nitra/cursor check ga` все одно їх запустить);
218
- * - якщо `conftest` є й полісі-каталог відсутній (нетипова інсталяція) — також `ℹ` skip;
219
- * - якщо є цільовий workflow і conftest — запускаємо `conftest test <workflow> -p <policy-dir>` і
220
- * повертаємо його exit-код, щоб порушення зупиняли lint-ga, як це робить actionlint/zizmor.
221
- *
222
- * Локальний `conftest` встановлюється через `brew install conftest` / `go install ...` — деталі в
223
- * https://www.conftest.dev/install/.
224
- * @returns {number} 0 — OK або skip, інакше — exit-код conftest
225
- */
226
- function runConftestStep() {
227
- const conftestBin = resolveCmd('conftest')
228
- if (!conftestBin) {
229
- console.log(
230
- '\nℹ conftest не знайдено в PATH — пропускаю PoC-перевірку структури workflow через Rego-полісі.\n' +
231
- ' Встанови, щоб запустити її локально: brew install conftest (macOS) або https://www.conftest.dev/install/'
232
- )
233
- return 0
234
- }
235
-
236
- if (!existsSync(GA_POLICY_DIR)) {
237
- console.log(`\nℹ Каталог Rego-полісі не знайдено (${GA_POLICY_DIR}) — пропускаю conftest.`)
238
- return 0
239
- }
240
-
241
- for (const target of CONFTEST_TARGETS) {
242
- if (!existsSync(target.workflow)) continue
243
- const code = runStep(`conftest (${target.label})`, conftestBin, [
244
- 'test',
245
- target.workflow,
246
- '-p',
247
- GA_POLICY_DIR,
248
- '--namespace',
249
- target.namespace,
250
- '--no-color'
251
- ])
252
- if (code !== 0) return code
253
- }
254
-
255
- return runConftestWorkflowCommon(conftestBin)
256
- }
257
-
258
- /**
259
- * Прогоняє `ga.workflow_common` на кожному `.github/workflows/*.yml` — універсальні перевірки
260
- * (concurrency, заборонені setup-bun/cache/install, shell line-continuation, checkout перед
261
- * локальним setup-bun-deps). Якщо директорії немає або файлів немає — мовчки skip.
262
- *
263
- * Викликаємо conftest на всіх файлах одним прогоном (`conftest test <files...>`) — швидше, ніж
264
- * по одному, і summary-лог зрозуміліший. Перший ненульовий exit-код повертаємо як результат.
265
- * @param {string} conftestBin абсолютний шлях до бінарника conftest
266
- * @returns {number} 0 — OK, інакше exit-код conftest
267
- */
268
- function runConftestWorkflowCommon(conftestBin) {
269
- const wfDir = '.github/workflows'
270
- if (!existsSync(wfDir)) return 0
271
- const ymlFiles = readdirSync(wfDir)
272
- .filter(f => f.endsWith('.yml'))
273
- .map(f => join(wfDir, f))
274
- if (ymlFiles.length === 0) return 0
275
-
276
- return runStep('conftest (workflow_common — усі workflow)', conftestBin, [
277
- 'test',
278
- ...ymlFiles,
279
- '-p',
280
- GA_POLICY_DIR,
281
- '--namespace',
282
- 'ga.workflow_common',
283
- '--no-color'
284
- ])
169
+ console.log('\n▶ check-ga (rego-полісі npm/policy/ga/ + JS cross-file перевірки)')
170
+ return await checkGa()
285
171
  }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Запускає `conftest test` на batched-списку файлів і повертає всі порушення
3
+ * у структурованому вигляді. Використовується з `check-*.mjs`-скриптів, де
4
+ * пер-документні правила винесені у `npm/policy/<rule>/<name>/` як rego-полісі
5
+ * (Rego-authoritative). JS у `check-*.mjs` робить cross-file частину (walking
6
+ * дерева, парність, kustomize-резолюція), а пер-документне валідаційне ядро
7
+ * делегується сюди — один спавн `conftest` на (`namespace`, `policyDir`),
8
+ * незалежно від кількості файлів. Це закриває дублювання JS↔rego і прибирає
9
+ * ризик дрифту (типу `spec.config` vs `spec.default.config` у
10
+ * `health_check_policy.rego`, що ми ловили cross-check тестами).
11
+ *
12
+ * Hard-fail на відсутність `conftest` у PATH — узгоджено з рішенням Plan B:
13
+ * якщо правило делегує свою логіку до Rego, а інструмент відсутній, тиха
14
+ * відмова приховує реальні порушення. Друкуємо install-hint (як `lint-rego.mjs`
15
+ * робить для opa/regal).
16
+ */
17
+ import { spawnSync } from 'node:child_process'
18
+ import { existsSync } from 'node:fs'
19
+ import { dirname, join } from 'node:path'
20
+ import { fileURLToPath } from 'node:url'
21
+
22
+ import { resolveCmd } from './resolve-cmd.mjs'
23
+
24
+ /** Каталог пакета `@nitra/cursor`, від якого ресолвимо вшиту директорію `policy/`. */
25
+ const PACKAGE_ROOT = dirname(dirname(dirname(fileURLToPath(import.meta.url))))
26
+
27
+ /** Шлях до кореня rego-полісі. У npm-tarball публікується через `files: ["policy"]`. */
28
+ const POLICY_ROOT = join(PACKAGE_ROOT, 'policy')
29
+
30
+ /**
31
+ * Друкує install-hint для conftest і кидає виняток, щоб викликана `check-*`
32
+ * команда ясно завершилась з кодом 1.
33
+ * @returns {never}
34
+ */
35
+ function failConftestMissing() {
36
+ throw new Error(
37
+ [
38
+ '❌ conftest не знайдено в PATH.',
39
+ ' Без нього не запускається пер-документна валідація через rego-полісі (npm/policy/).',
40
+ ' Встанови:',
41
+ ' macOS: brew install conftest',
42
+ ' Universal: https://www.conftest.dev/install/'
43
+ ].join('\n')
44
+ )
45
+ }
46
+
47
+ /**
48
+ * @typedef {object} ConftestViolation
49
+ * @property {string} filename абсолютний шлях до файла, що дав порушення (з output conftest)
50
+ * @property {string} message текст порушення (як у `deny` rego-пакета)
51
+ * @property {string} namespace namespace rego-пакета (наприклад `abie.base_deployment_preem`)
52
+ */
53
+
54
+ /**
55
+ * @typedef {object} ConftestBatchOptions
56
+ * @property {string} policyDirRel шлях до підкаталогу `npm/policy/...` (наприклад `abie/base_deployment_preem`)
57
+ * @property {string} namespace повне імʼя rego-пакета (наприклад `abie.base_deployment_preem`)
58
+ * @property {string[]} files список абсолютних шляхів файлів для перевірки (порожній — повертаємо порожньо)
59
+ * @property {string[]} [extraArgs] додаткові аргументи для conftest (наприклад `--combine` для крос-документних правил)
60
+ */
61
+
62
+ /**
63
+ * Виконує `conftest test` для всіх файлів одним спавном і повертає масив
64
+ * порушень. Якщо `files` порожній — повертає `[]` без спавна. Якщо `conftest`
65
+ * не у PATH — кидає виняток (hard fail, див. модульний docstring).
66
+ * @param {ConftestBatchOptions} opts параметри запуску
67
+ * @returns {ConftestViolation[]} масив порушень (порожній — все ок)
68
+ */
69
+ export function runConftestBatch(opts) {
70
+ if (opts.files.length === 0) return []
71
+ const conftestBin = resolveCmd('conftest')
72
+ if (!conftestBin) {
73
+ failConftestMissing()
74
+ }
75
+ const policyAbs = join(POLICY_ROOT, opts.policyDirRel)
76
+ if (!existsSync(policyAbs)) {
77
+ throw new Error(`runConftestBatch: rego-каталог не знайдено: ${policyAbs}`)
78
+ }
79
+ const args = [
80
+ 'test',
81
+ ...opts.files,
82
+ '-p',
83
+ policyAbs,
84
+ '--namespace',
85
+ opts.namespace,
86
+ '--output',
87
+ 'json',
88
+ '--no-color',
89
+ ...(opts.extraArgs ?? [])
90
+ ]
91
+ const result = spawnSync(conftestBin, args, { encoding: 'utf8' })
92
+ if (result.error) {
93
+ throw result.error
94
+ }
95
+ // conftest exit 1 = є failures (це валідно для нас); >1 = справжня помилка.
96
+ if (result.status !== 0 && result.status !== 1) {
97
+ throw new Error(
98
+ `conftest exit ${result.status}: ${(result.stderr || result.stdout || '').slice(0, 500)}`
99
+ )
100
+ }
101
+ /** @type {Array<{ filename: string, namespace: string, failures?: Array<{ msg: string }> }>} */
102
+ let parsed
103
+ try {
104
+ parsed = JSON.parse(result.stdout)
105
+ } catch {
106
+ throw new Error(`conftest stdout не парситься як JSON: ${(result.stdout || '').slice(0, 200)}`)
107
+ }
108
+ /** @type {ConftestViolation[]} */
109
+ const out = []
110
+ for (const entry of parsed) {
111
+ const failures = entry.failures ?? []
112
+ for (const f of failures) {
113
+ out.push({ filename: entry.filename, namespace: entry.namespace, message: f.msg })
114
+ }
115
+ }
116
+ return out
117
+ }
@@ -1,31 +0,0 @@
1
- # Порт перевірки `metadataNamespaceForbiddenViolation` з
2
- # `npm/scripts/check-k8s.mjs` (k8s.mdc): для файлів, які підключено до якогось
3
- # `kustomization.yaml` через `resources` / `patches` / `…`, поле
4
- # `metadata.namespace` забороняється — namespace задає сам kustomization.
5
- #
6
- # Запуск (локально, лише для одного kustomize-managed YAML):
7
- # conftest test path/to/manifest.yaml -p npm/policy/k8s/kustomize_managed \
8
- # --namespace k8s.kustomize_managed
9
- #
10
- # JS відбирає kustomize-managed файли через `collectKustomizeManagedRelPaths`
11
- # і викликає conftest з цією намеспейс. JS authoritative
12
- # (`check-k8s.mjs`: `metadataNamespaceForbiddenViolation`,
13
- # `failIfK8sPolicyNamespaceRulesViolated`).
14
- #
15
- # Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
16
- # Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`.
17
- package k8s.kustomize_managed
18
-
19
- import rego.v1
20
-
21
- namespace_forbidden_msg := concat(" ", [
22
- "metadata.namespace заборонено — namespace задає kustomization.yaml",
23
- "(поле namespace); файл підключено через resources / patches / …",
24
- "(k8s.mdc)",
25
- ])
26
-
27
- deny contains namespace_forbidden_msg if {
28
- meta := object.get(input, "metadata", null)
29
- is_object(meta)
30
- "namespace" in object.keys(meta)
31
- }
@@ -1,30 +0,0 @@
1
- # Тести для `k8s.kustomize_managed`. Запуск:
2
- # conftest verify -p npm/policy/k8s/kustomize_managed --namespace k8s.kustomize_managed
3
- package k8s.kustomize_managed_test
4
-
5
- import rego.v1
6
-
7
- import data.k8s.kustomize_managed
8
-
9
- test_deny_metadata_with_namespace if {
10
- count(kustomize_managed.deny) > 0 with input as {
11
- "apiVersion": "v1",
12
- "kind": "ConfigMap",
13
- "metadata": {"name": "cm", "namespace": "dev"},
14
- }
15
- }
16
-
17
- test_allow_metadata_without_namespace if {
18
- count(kustomize_managed.deny) == 0 with input as {
19
- "apiVersion": "v1",
20
- "kind": "ConfigMap",
21
- "metadata": {"name": "cm"},
22
- }
23
- }
24
-
25
- test_allow_no_metadata if {
26
- count(kustomize_managed.deny) == 0 with input as {
27
- "apiVersion": "v1",
28
- "kind": "ConfigMap",
29
- }
30
- }