@nitra/cursor 1.8.206 → 1.8.208
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 +36 -0
- package/mdc/js-run.mdc +49 -2
- package/package.json +1 -1
- package/policy/abie/health_check_policy/health_check_policy.rego +73 -0
- package/policy/abie/http_route_base/http_route_base.rego +45 -0
- package/policy/adr/settings_json/settings_json.rego +31 -0
- package/policy/adr/settings_local_json/settings_local_json.rego +28 -0
- package/policy/bun/bunfig/bunfig.rego +33 -0
- package/policy/bun/package_json/package_json.rego +94 -0
- package/policy/capacitor/package_json/package_json.rego +45 -0
- package/policy/ga/clean_ga_workflows/clean_ga_workflows.rego +0 -26
- package/policy/ga/clean_merged_branch/clean_merged_branch.rego +0 -25
- package/policy/ga/git_ai/git_ai.rego +0 -26
- package/policy/ga/lint_ga/lint_ga.rego +0 -26
- package/policy/ga/workflow_common/workflow_common.rego +161 -0
- package/policy/graphql/package_json/package_json.rego +35 -0
- package/policy/hasura/svc_hl/svc_hl.rego +27 -0
- package/policy/image_compress/package_json/package_json.rego +94 -0
- package/policy/js_bun_db/package_json/package_json.rego +28 -0
- package/policy/js_lint/lint_js_yml/lint_js_yml.rego +98 -0
- package/policy/js_lint/package_json/package_json.rego +137 -0
- package/policy/js_mssql/package_json/package_json.rego +57 -0
- package/policy/js_run/configmap/configmap.rego +45 -0
- package/policy/js_run/jsconfig/jsconfig.rego +66 -0
- package/policy/js_run/package_json/package_json.rego +31 -0
- package/policy/k8s/manifest/manifest.rego +130 -0
- package/policy/npm_module/emit_types_config/emit_types_config.rego +37 -0
- package/policy/npm_module/npm_package_json/npm_package_json.rego +55 -0
- package/policy/npm_module/npm_publish_yml/npm_publish_yml.rego +79 -0
- package/policy/npm_module/root_package_json/root_package_json.rego +28 -0
- package/policy/php/lint_php_yml/lint_php_yml.rego +32 -0
- package/policy/php/package_json/package_json.rego +19 -0
- package/policy/style_lint/lint_style_yml/lint_style_yml.rego +35 -0
- package/policy/style_lint/package_json/package_json.rego +49 -0
- package/policy/text/cspell/cspell.rego +91 -0
- package/policy/text/markdownlint/markdownlint.rego +21 -0
- package/policy/text/oxfmtrc/oxfmtrc.rego +90 -0
- package/policy/text/package_json/package_json.rego +88 -0
- package/policy/vue/package_json/package_json.rego +54 -0
- package/scripts/check-adr.mjs +3 -2
- package/scripts/check-bun.mjs +21 -117
- package/scripts/check-graphql.mjs +6 -45
- package/scripts/check-hasura.mjs +2 -3
- package/scripts/check-image-avif.mjs +3 -3
- package/scripts/check-image-compress.mjs +25 -132
- package/scripts/check-js-bun-db.mjs +3 -50
- package/scripts/check-js-run.mjs +84 -86
- package/scripts/check-k8s.mjs +4 -4
- package/scripts/check-npm-module.mjs +17 -8
- package/scripts/check-php.mjs +16 -51
- package/scripts/check-style-lint.mjs +28 -52
- package/scripts/check-text.mjs +47 -219
- package/scripts/check-vue.mjs +3 -16
- package/scripts/lint-conftest.mjs +351 -0
- package/scripts/lint-ga.mjs +39 -2
- package/scripts/run-shellcheck-text.mjs +2 -2
- package/scripts/utils/conn-file-rules.mjs +170 -0
|
@@ -14,21 +14,12 @@ import rego.v1
|
|
|
14
14
|
|
|
15
15
|
# ── Очікувані значення ─────────────────────────────────────────────────────
|
|
16
16
|
|
|
17
|
-
expected_concurrency_group := concat("", ["$", "{{ github.ref }}-$", "{{ github.workflow }}"])
|
|
18
|
-
|
|
19
17
|
expected_name := "Lint GA"
|
|
20
18
|
|
|
21
19
|
expected_branches := {"dev", "main"}
|
|
22
20
|
|
|
23
21
|
expected_push_paths := {".github/actions/**", ".github/workflows/**"}
|
|
24
22
|
|
|
25
|
-
# Шаблон повідомлення про відсутню `concurrency`-секцію — через `concat` для
|
|
26
|
-
# regal style/line-length.
|
|
27
|
-
concurrency_missing_template := concat(" ", [
|
|
28
|
-
"lint-ga.yml: відсутня секція concurrency —",
|
|
29
|
-
"додай concurrency.group: %s і cancel-in-progress: true (ga.mdc)",
|
|
30
|
-
])
|
|
31
|
-
|
|
32
23
|
# ── Аліаси на input ────────────────────────────────────────────────────────
|
|
33
24
|
#
|
|
34
25
|
# YAML 1.1 quirk: `on:` → boolean true → у конфтесті ключ "true".
|
|
@@ -69,23 +60,6 @@ deny contains msg if {
|
|
|
69
60
|
msg := "lint-ga.yml: on.push.paths має містити .github/actions/** і .github/workflows/** (ga.mdc)"
|
|
70
61
|
}
|
|
71
62
|
|
|
72
|
-
deny contains msg if {
|
|
73
|
-
not is_object(input.concurrency)
|
|
74
|
-
msg := sprintf(concurrency_missing_template, [expected_concurrency_group])
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
deny contains msg if {
|
|
78
|
-
is_object(input.concurrency)
|
|
79
|
-
input.concurrency.group != expected_concurrency_group
|
|
80
|
-
msg := sprintf("lint-ga.yml: concurrency.group має бути %s (ga.mdc)", [expected_concurrency_group])
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
deny contains msg if {
|
|
84
|
-
is_object(input.concurrency)
|
|
85
|
-
input.concurrency["cancel-in-progress"] != true
|
|
86
|
-
msg := "lint-ga.yml: concurrency.cancel-in-progress має бути true (ga.mdc)"
|
|
87
|
-
}
|
|
88
|
-
|
|
89
63
|
deny contains msg if {
|
|
90
64
|
not job
|
|
91
65
|
msg := "lint-ga.yml: jobs.lint-ga відсутній (ga.mdc)"
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Універсальні перевірки для будь-якого `.github/workflows/*.yml` (ga.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Порт `verifyNoDirectBunOrCache`, `verifyNoRunShellLineContinuationBackslash`,
|
|
4
|
+
# `verifyCheckoutBeforeLocalSetupBunDeps` та `validateConcurrencyOnRoot` з
|
|
5
|
+
# `npm/scripts/check-ga.mjs`. На відміну від `lint_ga`/`clean_ga_workflows`/
|
|
6
|
+
# `clean_merged_branch`/`git_ai`, цей пакет не привʼязаний до конкретного
|
|
7
|
+
# workflow — `conftest test` запускається на кожному файлі окремо з
|
|
8
|
+
# `--namespace ga.workflow_common`, і `input` — це окремий розпарсений YAML.
|
|
9
|
+
#
|
|
10
|
+
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
11
|
+
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
|
|
12
|
+
# (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
|
|
13
|
+
package ga.workflow_common
|
|
14
|
+
|
|
15
|
+
import rego.v1
|
|
16
|
+
|
|
17
|
+
# ── Очікувані значення ─────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
expected_concurrency_group := concat("", ["$", "{{ github.ref }}-$", "{{ github.workflow }}"])
|
|
20
|
+
|
|
21
|
+
# Локальні composite setup-bun-deps (ga.mdc) — два варіанти шляху:
|
|
22
|
+
# `.github/actions/...` (cursor-репо) і `npm/github-actions/...` (npm-пакет dev-локально).
|
|
23
|
+
local_setup_bun_markers := {
|
|
24
|
+
"./.github/actions/setup-bun-deps",
|
|
25
|
+
"./npm/github-actions/setup-bun-deps",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
# Заборонені підрядки в кроках `uses` та `run` (ga.mdc): дублюючі setup/cache/install,
|
|
29
|
+
# які мають бути всередині composite-action `setup-bun-deps`.
|
|
30
|
+
forbidden_step_substrings := {
|
|
31
|
+
"oven-sh/setup-bun": "використовуй .github/actions/setup-bun-deps замість oven-sh/setup-bun",
|
|
32
|
+
"actions/cache": "використовуй .github/actions/setup-bun-deps замість actions/cache",
|
|
33
|
+
"bun install": "використовуй .github/actions/setup-bun-deps замість bun install",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# Шаблони довгих повідомлень — через `concat`, щоб дотримуватися regal style/line-length.
|
|
37
|
+
|
|
38
|
+
concurrency_missing_template := concat(" ", [
|
|
39
|
+
"відсутня секція concurrency —",
|
|
40
|
+
"додай concurrency.group: %s і cancel-in-progress: true (ga.mdc)",
|
|
41
|
+
])
|
|
42
|
+
|
|
43
|
+
shell_continuation_template := concat(" ", [
|
|
44
|
+
"jobs.%s.steps[%d]: у run заборонено продовження рядків через зворотний сліш;",
|
|
45
|
+
"оформи як folded block (run: >-) (ga.mdc)",
|
|
46
|
+
])
|
|
47
|
+
|
|
48
|
+
setup_bun_no_checkout_template := concat(" ", [
|
|
49
|
+
"jobs.%s: перед локальним setup-bun-deps потрібен крок actions/checkout@v6 —",
|
|
50
|
+
"інакше runner не знайде action.yml (ga.mdc)",
|
|
51
|
+
])
|
|
52
|
+
|
|
53
|
+
# ── Аліаси на input ────────────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
# Усі jobs (з гарантією, що це обʼєкт) — щоб не падати на нетипових YAML.
|
|
56
|
+
jobs := input.jobs
|
|
57
|
+
|
|
58
|
+
# Плоский список усіх кроків з усіх jobs з метаданими — для перевірок, де job-id
|
|
59
|
+
# і позиція кроку нам потрібні в повідомленні (shell line continuation).
|
|
60
|
+
all_flat_steps contains entry if {
|
|
61
|
+
some job_id, step_index
|
|
62
|
+
step := jobs[job_id].steps[step_index]
|
|
63
|
+
entry := {"job_id": job_id, "step_index": step_index, "step": step}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# ── deny: заборонені setup-bun/cache/install у будь-якому кроці ────────────
|
|
67
|
+
|
|
68
|
+
deny contains msg if {
|
|
69
|
+
some entry in all_flat_steps
|
|
70
|
+
some pattern, hint in forbidden_step_substrings
|
|
71
|
+
step_uses_or_run_blob(entry.step) != ""
|
|
72
|
+
contains(step_uses_or_run_blob(entry.step), pattern)
|
|
73
|
+
msg := sprintf("jobs.%s.steps[%d]: %s (ga.mdc)", [entry.job_id, entry.step_index, hint])
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
# ── deny: shell-продовження `\` перед переносом рядка у `run:` ─────────────
|
|
77
|
+
#
|
|
78
|
+
# `\` + `\n` — bash line-continuation; у workflow замінюй на folded block `>-`
|
|
79
|
+
# без зворотних слішів (ga.mdc).
|
|
80
|
+
|
|
81
|
+
deny contains msg if {
|
|
82
|
+
some entry in all_flat_steps
|
|
83
|
+
run_text := step_run_text(entry.step)
|
|
84
|
+
regex.match(`\\\r?\n`, run_text)
|
|
85
|
+
msg := sprintf(shell_continuation_template, [entry.job_id, entry.step_index])
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# ── deny: setup-bun-deps без попереднього checkout у тому ж job ────────────
|
|
89
|
+
#
|
|
90
|
+
# Без `actions/checkout` локальний composite-action недоступний — runner не
|
|
91
|
+
# знайде `action.yml` у дереві. Перевіряємо порядок індексів кроків у кожному
|
|
92
|
+
# job: setup-bun-deps має бути після принаймні одного `actions/checkout@`.
|
|
93
|
+
|
|
94
|
+
deny contains msg if {
|
|
95
|
+
some job_id, job in jobs
|
|
96
|
+
first_setup := first_local_setup_bun_index(job)
|
|
97
|
+
first_setup >= 0
|
|
98
|
+
not has_checkout_before(job, first_setup)
|
|
99
|
+
msg := sprintf(setup_bun_no_checkout_template, [job_id])
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# ── deny: concurrency блок ─────────────────────────────────────────────────
|
|
103
|
+
#
|
|
104
|
+
# Дублює окремі per-workflow перевірки для clean-ga-workflows / clean-merged-branch /
|
|
105
|
+
# lint-ga / git-ai, але вкриває й решту workflow-файлів (apply-k8s, lint-js, …),
|
|
106
|
+
# для яких поки немає виділеної polysi.
|
|
107
|
+
|
|
108
|
+
deny contains msg if {
|
|
109
|
+
# `object.get(…, default)` повертає `false` коли ключа немає — інакше `not is_object(…)`
|
|
110
|
+
# над відсутнім полем дає `undefined`, не `true`, і правило мовчки не спрацьовує.
|
|
111
|
+
not is_object(object.get(input, "concurrency", false))
|
|
112
|
+
msg := sprintf(concurrency_missing_template, [expected_concurrency_group])
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
deny contains msg if {
|
|
116
|
+
is_object(object.get(input, "concurrency", false))
|
|
117
|
+
input.concurrency.group != expected_concurrency_group
|
|
118
|
+
msg := sprintf("concurrency.group має бути %s (ga.mdc)", [expected_concurrency_group])
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
deny contains msg if {
|
|
122
|
+
is_object(object.get(input, "concurrency", false))
|
|
123
|
+
input.concurrency["cancel-in-progress"] != true
|
|
124
|
+
msg := "concurrency.cancel-in-progress має бути true (ga.mdc)"
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# ── helpers ────────────────────────────────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
# Об'єднаний рядок `uses` + `run` для одного кроку — для substring-пошуку
|
|
130
|
+
# заборонених патернів. `run` може бути рядком або масивом рядків (YAML).
|
|
131
|
+
step_uses_or_run_blob(step) := blob if {
|
|
132
|
+
uses := object.get(step, "uses", "")
|
|
133
|
+
run_text := step_run_text(step)
|
|
134
|
+
blob := concat("\n", [uses, run_text])
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
# Текст `run:` як один рядок: підтримує рядкові та масивові форми (YAML).
|
|
138
|
+
step_run_text(step) := step.run if is_string(step.run)
|
|
139
|
+
|
|
140
|
+
else := concat("\n", [s | some s in step.run]) if is_array(step.run)
|
|
141
|
+
|
|
142
|
+
else := ""
|
|
143
|
+
|
|
144
|
+
# Індекс першого кроку з локальним setup-bun-deps; -1 якщо такого немає.
|
|
145
|
+
first_local_setup_bun_index(job) := min(indices) if {
|
|
146
|
+
indices := [i |
|
|
147
|
+
some i, step in job.steps
|
|
148
|
+
uses := object.get(step, "uses", "")
|
|
149
|
+
some marker in local_setup_bun_markers
|
|
150
|
+
contains(uses, marker)
|
|
151
|
+
]
|
|
152
|
+
count(indices) > 0
|
|
153
|
+
} else := -1
|
|
154
|
+
|
|
155
|
+
# Чи є в `job.steps[0..before]` крок `actions/checkout@…`.
|
|
156
|
+
has_checkout_before(job, before) if {
|
|
157
|
+
some i, step in job.steps
|
|
158
|
+
i < before
|
|
159
|
+
uses := object.get(step, "uses", "")
|
|
160
|
+
contains(uses, "actions/checkout@")
|
|
161
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Порт перевірки `package.json` з `npm/scripts/check-graphql.mjs` (graphql.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Запуск (локально, ЯКЩО проект містить `gql\`…\`` теги — gating робить JS-частина
|
|
4
|
+
# через oxc-parser-скан):
|
|
5
|
+
# conftest test package.json -p npm/policy/graphql --namespace graphql.package_json
|
|
6
|
+
#
|
|
7
|
+
# Перевіряє: `scripts.dump-schema` точно відповідає канону graphql.mdc.
|
|
8
|
+
#
|
|
9
|
+
# AST-скан коду на `gql\`…\`` template literals і FS-перевірки (наявність
|
|
10
|
+
# `.graphqlrc.yml`, `.vscode/extensions.json` з `graphql.vscode-graphql`) — у JS.
|
|
11
|
+
#
|
|
12
|
+
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
13
|
+
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
|
|
14
|
+
# (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
|
|
15
|
+
package graphql.package_json
|
|
16
|
+
|
|
17
|
+
import rego.v1
|
|
18
|
+
|
|
19
|
+
required_dump_schema := concat("", [
|
|
20
|
+
"bunx graphqurl http://localhost:4040/v1/graphql ",
|
|
21
|
+
"-H 'X-Hasura-Admin-Secret: secret' --introspect > schema.graphql",
|
|
22
|
+
])
|
|
23
|
+
|
|
24
|
+
deny contains msg if {
|
|
25
|
+
scripts := object.get(input, "scripts", {})
|
|
26
|
+
not "dump-schema" in object.keys(scripts)
|
|
27
|
+
msg := "package.json: відсутній scripts.dump-schema (graphql.mdc)"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
deny contains msg if {
|
|
31
|
+
dump := object.get(object.get(input, "scripts", {}), "dump-schema", "")
|
|
32
|
+
dump != ""
|
|
33
|
+
dump != required_dump_schema
|
|
34
|
+
msg := sprintf("package.json: scripts.dump-schema має бути канонічним з graphql.mdc (зараз %q)", [dump])
|
|
35
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Порт мінімальної структурної перевірки `hasura/k8s/base/svc-hl.yaml` з
|
|
2
|
+
# `npm/scripts/check-hasura.mjs` (hasura.mdc): для кожного Service у файлі
|
|
3
|
+
# `metadata.name` має закінчуватись на `-h` (headless-сервіс Hasura).
|
|
4
|
+
#
|
|
5
|
+
# Запуск (локально):
|
|
6
|
+
# conftest test hasura/k8s/base/svc-hl.yaml -p npm/policy/hasura \
|
|
7
|
+
# --namespace hasura.svc_hl
|
|
8
|
+
#
|
|
9
|
+
# Решта логіки `check-hasura.mjs` (звірення `HASURA_GRAPHQL_ENDPOINT` в `.env`-файлах
|
|
10
|
+
# з `<service>.<namespace>.svc.<cluster>` через regex по всьому дереву репо, gating
|
|
11
|
+
# на `repository` у кореневому `package.json`) — у JS: вона потребує текстового
|
|
12
|
+
# парсингу `.env`-файлів, обходу дерева й cross-file resolution.
|
|
13
|
+
#
|
|
14
|
+
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
15
|
+
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
|
|
16
|
+
# (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
|
|
17
|
+
package hasura.svc_hl
|
|
18
|
+
|
|
19
|
+
import rego.v1
|
|
20
|
+
|
|
21
|
+
deny contains msg if {
|
|
22
|
+
input.kind == "Service"
|
|
23
|
+
name := object.get(object.get(input, "metadata", {}), "name", "")
|
|
24
|
+
name != ""
|
|
25
|
+
not endswith(name, "-h")
|
|
26
|
+
msg := sprintf("hasura svc-hl.yaml: Service %q має закінчуватись на `-h` (hasura.mdc / k8s.mdc)", [name])
|
|
27
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Порт перевірки `package.json` з `npm/scripts/check-image-compress.mjs`
|
|
2
|
+
# (image-compress.mdc).
|
|
3
|
+
#
|
|
4
|
+
# Запуск (локально):
|
|
5
|
+
# conftest test package.json -p npm/policy/image_compress \
|
|
6
|
+
# --namespace image_compress.package_json
|
|
7
|
+
#
|
|
8
|
+
# Перевіряє: скрипт `lint-image` викликає `npx @nitra/minify-image` з `--src=.`
|
|
9
|
+
# і `--write`, без `--avif` (AVIF — окреме правило); агрегатор `lint` (якщо є)
|
|
10
|
+
# містить `bun run lint-image`; `@nitra/minify-image` НЕ в dependencies / devDependencies.
|
|
11
|
+
#
|
|
12
|
+
# FS-перевірки (`.minify-image-cache.tsv` legacy-файл, `.gitignore` правил для
|
|
13
|
+
# `.n-minify-image.tsv`) — у JS.
|
|
14
|
+
#
|
|
15
|
+
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
16
|
+
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
|
|
17
|
+
# (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
|
|
18
|
+
package image_compress.package_json
|
|
19
|
+
|
|
20
|
+
import rego.v1
|
|
21
|
+
|
|
22
|
+
minify_pkg := "@nitra/minify-image"
|
|
23
|
+
|
|
24
|
+
dep_template := concat(" ", [
|
|
25
|
+
"package.json: %q не повинен бути в %s —",
|
|
26
|
+
"використовуй npx (image-compress.mdc)",
|
|
27
|
+
])
|
|
28
|
+
|
|
29
|
+
avif_in_lint_image_template := concat(" ", [
|
|
30
|
+
"package.json: lint-image не має містити `--avif` —",
|
|
31
|
+
"AVIF-генерацію виконує check image-avif (image-compress.mdc)",
|
|
32
|
+
])
|
|
33
|
+
|
|
34
|
+
# ── deny: lint-image ──────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
deny contains msg if {
|
|
37
|
+
scripts := object.get(input, "scripts", {})
|
|
38
|
+
not "lint-image" in object.keys(scripts)
|
|
39
|
+
msg := "package.json: відсутній scripts.lint-image (image-compress.mdc)"
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
deny contains msg if {
|
|
43
|
+
lint_image := object.get(object.get(input, "scripts", {}), "lint-image", "")
|
|
44
|
+
lint_image != ""
|
|
45
|
+
not contains(lint_image, sprintf("npx %s", [minify_pkg]))
|
|
46
|
+
msg := sprintf("package.json: lint-image має викликати `npx %s` (image-compress.mdc)", [minify_pkg])
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
deny contains msg if {
|
|
50
|
+
lint_image := object.get(object.get(input, "scripts", {}), "lint-image", "")
|
|
51
|
+
contains(lint_image, sprintf("npx %s", [minify_pkg]))
|
|
52
|
+
not has_src_flag(lint_image)
|
|
53
|
+
msg := "package.json: lint-image має містити `--src=.` (image-compress.mdc)"
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
deny contains msg if {
|
|
57
|
+
lint_image := object.get(object.get(input, "scripts", {}), "lint-image", "")
|
|
58
|
+
contains(lint_image, sprintf("npx %s", [minify_pkg]))
|
|
59
|
+
not contains(lint_image, "--write")
|
|
60
|
+
msg := "package.json: lint-image має містити `--write` (image-compress.mdc)"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
deny contains avif_in_lint_image_template if {
|
|
64
|
+
lint_image := object.get(object.get(input, "scripts", {}), "lint-image", "")
|
|
65
|
+
contains(lint_image, "--avif")
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
# ── deny: агрегований `lint` має кликати `bun run lint-image` ─────────────
|
|
69
|
+
|
|
70
|
+
deny contains msg if {
|
|
71
|
+
"lint-image" in object.keys(object.get(input, "scripts", {}))
|
|
72
|
+
lint := object.get(object.get(input, "scripts", {}), "lint", "")
|
|
73
|
+
lint != ""
|
|
74
|
+
not contains(lint, "bun run lint-image")
|
|
75
|
+
msg := "package.json: агрегований `lint` має містити `bun run lint-image` (image-compress.mdc)"
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# ── deny: `@nitra/minify-image` НЕ в dependencies/devDependencies ────────
|
|
79
|
+
|
|
80
|
+
deny contains msg if {
|
|
81
|
+
minify_pkg in object.keys(object.get(input, "dependencies", {}))
|
|
82
|
+
msg := sprintf(dep_template, [minify_pkg, "dependencies"])
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
deny contains msg if {
|
|
86
|
+
minify_pkg in object.keys(object.get(input, "devDependencies", {}))
|
|
87
|
+
msg := sprintf(dep_template, [minify_pkg, "devDependencies"])
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# ── helpers ────────────────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
has_src_flag(s) if contains(s, "--src=.")
|
|
93
|
+
|
|
94
|
+
has_src_flag(s) if contains(s, "--src .")
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Порт перевірки залежностей `package.json` з `npm/scripts/check-js-bun-db.mjs`
|
|
2
|
+
# (js-bun-db.mdc).
|
|
3
|
+
#
|
|
4
|
+
# Запуск (локально, для будь-якого `package.json` у дереві):
|
|
5
|
+
# conftest test path/to/package.json -p npm/policy/js_bun_db \
|
|
6
|
+
# --namespace js_bun_db.package_json
|
|
7
|
+
#
|
|
8
|
+
# Перевіряє: у `dependencies` не повинно бути `pg`, `pg-format`, `mysql2` —
|
|
9
|
+
# заміна на Bun native SQL (https://bun.com/docs/runtime/sql).
|
|
10
|
+
#
|
|
11
|
+
# AST-скан коду (`new SQL(...)` всередині функцій, `unsafe()` без маркера
|
|
12
|
+
# `// allow-unsafe`, pg-leftover виклики, динамічні `IN (…)` через `.join(',')`)
|
|
13
|
+
# лишається у JS (потребує парсингу `.js` / `.ts` через oxc-parser).
|
|
14
|
+
#
|
|
15
|
+
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
16
|
+
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
|
|
17
|
+
# (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
|
|
18
|
+
package js_bun_db.package_json
|
|
19
|
+
|
|
20
|
+
import rego.v1
|
|
21
|
+
|
|
22
|
+
forbidden_dependencies := {"pg", "pg-format", "mysql2"}
|
|
23
|
+
|
|
24
|
+
deny contains msg if {
|
|
25
|
+
some pkg_name in forbidden_dependencies
|
|
26
|
+
pkg_name in object.keys(object.get(input, "dependencies", {}))
|
|
27
|
+
msg := sprintf("dependencies містить заборонений %q — заміни на Bun native SQL (js-bun-db.mdc)", [pkg_name])
|
|
28
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Порт перевірки `.github/workflows/lint-js.yml` з `npm/scripts/check-js-lint.mjs`
|
|
2
|
+
# (js-lint.mdc) — структурні очікування `verifyLintJsWorkflowStructure`.
|
|
3
|
+
#
|
|
4
|
+
# Запуск (локально):
|
|
5
|
+
# conftest test .github/workflows/lint-js.yml -p npm/policy/js_lint \
|
|
6
|
+
# --namespace js_lint.lint_js_yml
|
|
7
|
+
#
|
|
8
|
+
# Перевіряє: є крок `actions/checkout@v6` з `with.persist-credentials: false`,
|
|
9
|
+
# є крок `./.github/actions/setup-bun-deps`, у `run` є `bunx oxlint`, `bunx eslint .`,
|
|
10
|
+
# `bunx jscpd .`, у `run` НЕМАЄ `oxlint --fix` чи `eslint --fix` (CI не повинен
|
|
11
|
+
# редагувати код).
|
|
12
|
+
#
|
|
13
|
+
# Універсальні workflow-перевірки (concurrency, заборонені setup-bun/cache, shell
|
|
14
|
+
# line-continuation) — у `ga.workflow_common`. Дубль JS-перевірок у `lint.yml` —
|
|
15
|
+
# у JS-частині `check-js-lint.mjs` (потребує другого workflow-файлу).
|
|
16
|
+
#
|
|
17
|
+
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
18
|
+
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
|
|
19
|
+
# (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
|
|
20
|
+
package js_lint.lint_js_yml
|
|
21
|
+
|
|
22
|
+
import rego.v1
|
|
23
|
+
|
|
24
|
+
# Усі кроки з усіх jobs — для substring-перевірок.
|
|
25
|
+
all_steps contains step if {
|
|
26
|
+
some job in object.get(input, "jobs", {})
|
|
27
|
+
some step in object.get(job, "steps", [])
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
all_uses_blob := concat("\n", [u |
|
|
31
|
+
some step in all_steps
|
|
32
|
+
u := object.get(step, "uses", "")
|
|
33
|
+
])
|
|
34
|
+
|
|
35
|
+
all_run_blob := concat("\n", [r |
|
|
36
|
+
some step in all_steps
|
|
37
|
+
r := step_run_text(step)
|
|
38
|
+
])
|
|
39
|
+
|
|
40
|
+
# ── deny: required uses ────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
deny contains msg if {
|
|
43
|
+
not contains(all_uses_blob, "actions/checkout@v6")
|
|
44
|
+
msg := "lint-js.yml: відсутній крок uses: actions/checkout@v6 (js-lint.mdc)"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
deny contains msg if {
|
|
48
|
+
not contains(all_uses_blob, "./.github/actions/setup-bun-deps")
|
|
49
|
+
msg := "lint-js.yml: відсутній крок uses: ./.github/actions/setup-bun-deps (js-lint.mdc)"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
deny contains msg if {
|
|
53
|
+
not has_checkout_persist_credentials_false
|
|
54
|
+
msg := "lint-js.yml: actions/checkout@v6 має бути з with.persist-credentials: false (js-lint.mdc)"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# ── deny: required run substrings ─────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
deny contains msg if {
|
|
60
|
+
not contains(all_run_blob, "bunx oxlint")
|
|
61
|
+
msg := "lint-js.yml: у run немає `bunx oxlint` (js-lint.mdc)"
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
deny contains msg if {
|
|
65
|
+
not contains(all_run_blob, "bunx eslint .")
|
|
66
|
+
msg := "lint-js.yml: у run немає `bunx eslint .` (js-lint.mdc)"
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
deny contains msg if {
|
|
70
|
+
not contains(all_run_blob, "bunx jscpd .")
|
|
71
|
+
msg := "lint-js.yml: у run немає `bunx jscpd .` (js-lint.mdc)"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# ── deny: --fix у CI заборонено ───────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
deny contains msg if {
|
|
77
|
+
regex.match(`bunx\s+oxlint[^\n]*--fix`, all_run_blob)
|
|
78
|
+
msg := "lint-js.yml: у run є oxlint з `--fix` (у CI заборонено) (js-lint.mdc)"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
deny contains msg if {
|
|
82
|
+
contains(all_run_blob, "eslint --fix")
|
|
83
|
+
msg := "lint-js.yml: у run є `eslint --fix` (у CI заборонено) (js-lint.mdc)"
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# ── helpers ────────────────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
has_checkout_persist_credentials_false if {
|
|
89
|
+
some step in all_steps
|
|
90
|
+
contains(object.get(step, "uses", ""), "actions/checkout@v6")
|
|
91
|
+
step.with["persist-credentials"] == false
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
step_run_text(step) := step.run if is_string(step.run)
|
|
95
|
+
|
|
96
|
+
else := concat("\n", [s | some s in step.run]) if is_array(step.run)
|
|
97
|
+
|
|
98
|
+
else := ""
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Порт перевірок `package.json` з `npm/scripts/check-js-lint.mjs` (js-lint.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Запуск (локально):
|
|
4
|
+
# conftest test package.json -p npm/policy/js_lint --namespace js_lint.package_json
|
|
5
|
+
#
|
|
6
|
+
# Перевіряє: канонічний `lint-js` скрипт, `@nitra/eslint-config` ≥ 3.9.2 у
|
|
7
|
+
# `devDependencies`, `engines.node >= 24`, `engines.bun >= 1.3`, `type: "module"`.
|
|
8
|
+
#
|
|
9
|
+
# Перевірка `.oxlintrc.json` проти канонічного JSON (`utils/oxlint-canonical.json`)
|
|
10
|
+
# і дубля JS-перевірок у `lint.yml` — у JS (потребує читання другого файлу
|
|
11
|
+
# і порівняння глибокої структури проти embedded snapshot).
|
|
12
|
+
#
|
|
13
|
+
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
14
|
+
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
|
|
15
|
+
# (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
|
|
16
|
+
package js_lint.package_json
|
|
17
|
+
|
|
18
|
+
import rego.v1
|
|
19
|
+
|
|
20
|
+
canonical_lint_js := "bunx oxlint --fix && bunx eslint --fix . && bunx jscpd ."
|
|
21
|
+
|
|
22
|
+
# ── deny: `lint-js` скрипт ─────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
deny contains msg if {
|
|
25
|
+
scripts := object.get(input, "scripts", {})
|
|
26
|
+
not "lint-js" in object.keys(scripts)
|
|
27
|
+
msg := "package.json: відсутній скрипт `lint-js` (js-lint.mdc)"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
deny contains msg if {
|
|
31
|
+
lint_js := object.get(object.get(input, "scripts", {}), "lint-js", "")
|
|
32
|
+
lint_js != ""
|
|
33
|
+
normalize_lint_js(lint_js) != canonical_lint_js
|
|
34
|
+
msg := sprintf("package.json: lint-js має бути %q (js-lint.mdc)", [canonical_lint_js])
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# ── deny: type: "module" ──────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
deny contains msg if {
|
|
40
|
+
object.get(input, "type", null) != "module"
|
|
41
|
+
msg := "package.json: \"type\" має бути \"module\" (js-lint.mdc)"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# ── deny: engines ──────────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
deny contains msg if {
|
|
47
|
+
engines := object.get(input, "engines", {})
|
|
48
|
+
not engines_node_meets(object.get(engines, "node", ""))
|
|
49
|
+
msg := "package.json: engines.node має бути >= 24 (js-lint.mdc)"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
deny contains msg if {
|
|
53
|
+
engines := object.get(input, "engines", {})
|
|
54
|
+
not engines_bun_meets(object.get(engines, "bun", ""))
|
|
55
|
+
msg := "package.json: engines.bun має бути >= 1.3 (js-lint.mdc)"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# ── deny: @nitra/eslint-config ≥ 3.9.2 ────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
deny contains msg if {
|
|
61
|
+
dev := object.get(input, "devDependencies", {})
|
|
62
|
+
not "@nitra/eslint-config" in object.keys(dev)
|
|
63
|
+
msg := "package.json: відсутній @nitra/eslint-config у devDependencies (js-lint.mdc)"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
deny contains msg if {
|
|
67
|
+
range := object.get(object.get(input, "devDependencies", {}), "@nitra/eslint-config", "")
|
|
68
|
+
range != ""
|
|
69
|
+
not eslint_config_meets_min(range)
|
|
70
|
+
msg := sprintf("package.json: @nitra/eslint-config має бути >= 3.9.2 (зараз %q) (js-lint.mdc)", [range])
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# ── helpers ────────────────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
# Нормалізація `lint-js`: trim + одиничні пробіли (як у JS).
|
|
76
|
+
normalize_lint_js(s) := regex.replace(trim_space(s), `\s+`, " ")
|
|
77
|
+
|
|
78
|
+
# `engines.node`: дозволяється `>=24`, `^24`, `24.x`, `24.0.0` тощо. Дістаємо
|
|
79
|
+
# першу мажорну цифру; вона має бути ≥ 24.
|
|
80
|
+
engines_node_meets(spec) if {
|
|
81
|
+
major := first_major(spec)
|
|
82
|
+
major >= 24
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
# `engines.bun`: дозволяється `>=1.3`, `^1.3.0`, `1.3.x` тощо. Перша мажор-мінор
|
|
86
|
+
# пара має бути ≥ 1.3.
|
|
87
|
+
engines_bun_meets(spec) if {
|
|
88
|
+
parts := split_to_numbers(spec)
|
|
89
|
+
count(parts) >= 2
|
|
90
|
+
parts[0] > 1
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
engines_bun_meets(spec) if {
|
|
94
|
+
parts := split_to_numbers(spec)
|
|
95
|
+
count(parts) >= 2
|
|
96
|
+
parts[0] == 1
|
|
97
|
+
parts[1] >= 3
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
# `@nitra/eslint-config`: ≥ 3.9.2; `workspace:*` теж OK.
|
|
101
|
+
eslint_config_meets_min(range) if startswith(trim_space(range), "workspace:")
|
|
102
|
+
|
|
103
|
+
eslint_config_meets_min(range) if {
|
|
104
|
+
parts := split_to_numbers(range)
|
|
105
|
+
count(parts) >= 3
|
|
106
|
+
parts[0] > 3
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
eslint_config_meets_min(range) if {
|
|
110
|
+
parts := split_to_numbers(range)
|
|
111
|
+
count(parts) >= 3
|
|
112
|
+
parts[0] == 3
|
|
113
|
+
parts[1] > 9
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
eslint_config_meets_min(range) if {
|
|
117
|
+
parts := split_to_numbers(range)
|
|
118
|
+
count(parts) >= 3
|
|
119
|
+
parts[0] == 3
|
|
120
|
+
parts[1] == 9
|
|
121
|
+
parts[2] >= 2
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
# Перша мажорна цифра з рядка-діапазону (наприклад `^24.1.0` → 24).
|
|
125
|
+
first_major(spec) := major if {
|
|
126
|
+
parts := split_to_numbers(spec)
|
|
127
|
+
count(parts) >= 1
|
|
128
|
+
major := parts[0]
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# Розкидати рядок версії на список чисел (відкидаючи range-оператори і нечислові
|
|
132
|
+
# фрагменти). `^24.1.0` → [24, 1, 0]; `>=1.3` → [1, 3]; `workspace:*` → [].
|
|
133
|
+
split_to_numbers(spec) := nums if {
|
|
134
|
+
tokens := regex.split(`\D+`, spec)
|
|
135
|
+
non_empty := [t | some t in tokens; t != ""]
|
|
136
|
+
nums := [n | some t in non_empty; n := to_number(t)]
|
|
137
|
+
}
|