@nitra/cursor 1.13.14 → 1.13.25
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 +85 -0
- package/package.json +1 -1
- package/rules/abie/abie.mdc +1 -4
- package/rules/abie/policy/clean_merged_ignore_branches/clean_merged_ignore_branches.rego +21 -40
- package/rules/abie/policy/clean_merged_ignore_branches/template/clean-merged-branch.yml.snippet.yml +9 -0
- package/rules/docker/docker.mdc +2 -50
- package/rules/docker/policy/lint_docker_yml/lint_docker_yml.rego +33 -47
- package/rules/docker/policy/lint_docker_yml/template/lint-docker.yml.snippet.yml +41 -0
- package/rules/docker/policy/package_json/package_json.rego +11 -28
- package/rules/docker/policy/package_json/template/package.json.snippet.json +1 -0
- package/rules/image-avif/policy/package_json/package_json.rego +21 -35
- package/rules/image-avif/policy/package_json/template/package.json.deny.json +5 -0
- package/rules/image-compress/image-compress.mdc +2 -8
- package/rules/image-compress/policy/package_json/package_json.rego +24 -65
- package/rules/image-compress/policy/package_json/template/package.json.contains.json +5 -0
- package/rules/image-compress/policy/package_json/template/package.json.deny.json +8 -0
- package/rules/js-bun-db/policy/package_json/package_json.rego +8 -21
- package/rules/js-bun-db/policy/package_json/template/package.json.deny.json +7 -0
- package/rules/js-bun-redis/policy/package_json/package_json.rego +8 -30
- package/rules/js-bun-redis/policy/package_json/template/package.json.deny.json +12 -0
- package/rules/js-lint/policy/jscpd/jscpd.rego +29 -23
- package/rules/js-lint/policy/jscpd/template/.jscpd.json.snippet.json +6 -0
- package/rules/js-lint/policy/lint_js_yml/lint_js_yml.rego +39 -47
- package/rules/js-lint/policy/lint_js_yml/template/lint-js.yml.snippet.yml +44 -0
- package/rules/js-lint/policy/package_json/package_json.rego +22 -42
- package/rules/js-lint/policy/package_json/template/package.json.snippet.json +6 -0
- package/rules/js-lint/policy/vscode_extensions/template/extensions.json.snippet.json +7 -0
- package/rules/js-lint/policy/vscode_extensions/vscode_extensions.rego +4 -17
- package/rules/js-run/policy/configmap/configmap.rego +11 -35
- package/rules/js-run/policy/configmap/template/configmap.yaml.contains.yml +4 -0
- package/rules/js-run/policy/jsconfig/jsconfig.rego +39 -46
- package/rules/js-run/policy/jsconfig/template/jsconfig.json.snippet.json +10 -0
- package/rules/js-run/policy/package_json/package_json.rego +10 -21
- package/rules/js-run/policy/package_json/template/package.json.deny.json +10 -0
- package/rules/npm-module/npm-module.mdc +7 -36
- package/rules/npm-module/policy/emit_types_config/emit_types_config.rego +18 -27
- package/rules/npm-module/policy/emit_types_config/template/tsconfig.emit-types.json.snippet.json +9 -0
- package/rules/npm-module/policy/npm_package_json/npm_package_json.rego +20 -30
- package/rules/npm-module/policy/npm_package_json/template/package.json.snippet.json +1 -0
- package/rules/npm-module/policy/npm_publish_yml/npm_publish_yml.rego +46 -38
- package/rules/npm-module/policy/npm_publish_yml/template/npm-publish.yml.snippet.yml +34 -0
- package/rules/npm-module/policy/root_package_json/root_package_json.rego +17 -17
- package/rules/npm-module/policy/root_package_json/template/package.json.snippet.json +1 -0
- package/rules/php/php.mdc +2 -56
- package/rules/php/policy/lint_php_yml/lint_php_yml.rego +15 -13
- package/rules/php/policy/lint_php_yml/template/lint-php.yml.snippet.yml +47 -0
- package/rules/php/policy/package_json/package_json.rego +9 -12
- package/rules/php/policy/package_json/template/package.json.contains.json +5 -0
- package/rules/style-lint/policy/lint_style_yml/lint_style_yml.rego +14 -16
- package/rules/style-lint/policy/lint_style_yml/template/lint-style.yml.snippet.yml +39 -0
- package/rules/style-lint/policy/package_json/package_json.rego +22 -30
- package/rules/style-lint/policy/package_json/template/package.json.contains.json +5 -0
- package/rules/style-lint/policy/package_json/template/package.json.snippet.json +5 -0
- package/rules/style-lint/policy/vscode_extensions/template/extensions.json.snippet.json +1 -0
- package/rules/style-lint/policy/vscode_extensions/vscode_extensions.rego +5 -15
- package/rules/style-lint/policy/vscode_settings/template/settings.json.snippet.json +5 -0
- package/rules/style-lint/policy/vscode_settings/vscode_settings.rego +7 -16
- package/rules/text/policy/cspell/cspell.rego +39 -59
- package/rules/text/policy/cspell/template/.cspell.json.contains.json +3 -0
- package/rules/text/policy/cspell/template/.cspell.json.deny.json +5 -0
- package/rules/text/policy/cspell/template/.cspell.json.snippet.json +12 -0
- package/rules/text/policy/markdownlint/markdownlint.rego +37 -50
- package/rules/text/policy/markdownlint/template/.markdownlint-cli2.jsonc.snippet.jsonc +11 -0
- package/rules/text/policy/oxfmtrc/oxfmtrc.rego +23 -58
- package/rules/text/policy/oxfmtrc/template/.oxfmtrc.json.snippet.json +12 -0
- package/rules/text/policy/package_json/package_json.rego +14 -52
- package/rules/text/policy/package_json/template/package.json.deny.json +15 -0
- package/rules/text/policy/vscode_extensions/template/extensions.json.snippet.json +7 -0
- package/rules/text/policy/vscode_extensions/vscode_extensions.rego +4 -28
- package/rules/text/policy/vscode_settings/template/settings.json.snippet.json +9 -0
- package/rules/text/policy/vscode_settings/vscode_settings.rego +20 -42
|
@@ -1,71 +1,48 @@
|
|
|
1
|
-
#
|
|
2
|
-
# (image-compress.mdc).
|
|
1
|
+
# Перевірка `package.json` (image-compress.mdc).
|
|
3
2
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
# --namespace image_compress.package_json
|
|
3
|
+
# Канон надходить через --data: { "template": { "contains": ..., "deny": ... } }
|
|
4
|
+
# Структура --data сформована з template/package.json.{contains,deny}.json.
|
|
7
5
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
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).
|
|
6
|
+
# Логіка, що ЛИШАЄТЬСЯ у rego (inverse-patterns, не виносяться у template):
|
|
7
|
+
# - `--avif` ЗАБОРОНЕНИЙ підрядок у `lint-image` (anti-contains);
|
|
8
|
+
# - агрегатор `lint` (якщо `lint-image` присутній) має містити `bun run lint-image`.
|
|
18
9
|
package image_compress.package_json
|
|
19
10
|
|
|
20
11
|
import rego.v1
|
|
21
12
|
|
|
22
|
-
|
|
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 ──────────────────────────────────────────────────────
|
|
13
|
+
# ── deny: scripts.<name> має містити кожен substring з template.contains ─
|
|
35
14
|
|
|
36
15
|
deny contains msg if {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
16
|
+
some script_name, needles in data.template.contains.scripts
|
|
17
|
+
actual := object.get(object.get(input, "scripts", {}), script_name, "")
|
|
18
|
+
some needle in needles
|
|
19
|
+
not contains(actual, needle)
|
|
20
|
+
msg := sprintf("package.json: scripts.%s має містити %q (image-compress.mdc)", [script_name, needle])
|
|
40
21
|
}
|
|
41
22
|
|
|
42
|
-
deny
|
|
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
|
-
}
|
|
23
|
+
# ── deny: top-level deps/devDeps з template.deny ─────────────────────────
|
|
48
24
|
|
|
49
25
|
deny contains msg if {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
msg := "package.json: lint-image має містити `--src=.` (image-compress.mdc)"
|
|
26
|
+
some pkg, reason in data.template.deny.dependencies
|
|
27
|
+
pkg in object.keys(object.get(input, "dependencies", {}))
|
|
28
|
+
msg := sprintf("package.json: dependencies.%s — %s", [pkg, reason])
|
|
54
29
|
}
|
|
55
30
|
|
|
56
31
|
deny contains msg if {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
msg := "package.json: lint-image має містити `--write` (image-compress.mdc)"
|
|
32
|
+
some pkg, reason in data.template.deny.devDependencies
|
|
33
|
+
pkg in object.keys(object.get(input, "devDependencies", {}))
|
|
34
|
+
msg := sprintf("package.json: devDependencies.%s — %s", [pkg, reason])
|
|
61
35
|
}
|
|
62
36
|
|
|
63
|
-
deny contains
|
|
37
|
+
# ── deny: `--avif` заборонений у `lint-image` (anti-contains, у rego) ────
|
|
38
|
+
|
|
39
|
+
deny contains msg if {
|
|
64
40
|
lint_image := object.get(object.get(input, "scripts", {}), "lint-image", "")
|
|
65
41
|
contains(lint_image, "--avif")
|
|
42
|
+
msg := "package.json: lint-image не має містити `--avif` — AVIF-генерацію виконує check image-avif (image-compress.mdc)"
|
|
66
43
|
}
|
|
67
44
|
|
|
68
|
-
# ── deny:
|
|
45
|
+
# ── deny: агрегатор `lint` (якщо `lint-image` є) ─────────────────────────
|
|
69
46
|
|
|
70
47
|
deny contains msg if {
|
|
71
48
|
"lint-image" in object.keys(object.get(input, "scripts", {}))
|
|
@@ -74,21 +51,3 @@ deny contains msg if {
|
|
|
74
51
|
not contains(lint, "bun run lint-image")
|
|
75
52
|
msg := "package.json: агрегований `lint` має містити `bun run lint-image` (image-compress.mdc)"
|
|
76
53
|
}
|
|
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 .")
|
|
@@ -1,28 +1,15 @@
|
|
|
1
|
-
#
|
|
2
|
-
# (js-bun-db.mdc).
|
|
1
|
+
# Перевірка `dependencies` (js-bun-db.mdc).
|
|
3
2
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
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).
|
|
3
|
+
# Канон надходить через --data: { "template": { "deny": ... } }
|
|
4
|
+
# Структура --data сформована з template/package.json.deny.json.
|
|
5
|
+
# AST-скан коду (`new SQL(...)` у функціях, `unsafe()` без маркера, pg-leftover,
|
|
6
|
+
# динамічні `IN (…)` через `.join(',')`) лишається у JS.
|
|
18
7
|
package js_bun_db.package_json
|
|
19
8
|
|
|
20
9
|
import rego.v1
|
|
21
10
|
|
|
22
|
-
forbidden_dependencies := {"pg", "pg-format", "mysql2"}
|
|
23
|
-
|
|
24
11
|
deny contains msg if {
|
|
25
|
-
some
|
|
26
|
-
|
|
27
|
-
msg := sprintf("dependencies
|
|
12
|
+
some pkg, reason in data.template.deny.dependencies
|
|
13
|
+
pkg in object.keys(object.get(input, "dependencies", {}))
|
|
14
|
+
msg := sprintf("dependencies.%s — %s", [pkg, reason])
|
|
28
15
|
}
|
|
@@ -1,37 +1,15 @@
|
|
|
1
|
-
# Перевірка `dependencies`
|
|
2
|
-
# `npm/policy/js_bun_db/package_json/package_json.rego`.
|
|
1
|
+
# Перевірка `dependencies` (js-bun-redis.mdc).
|
|
3
2
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# Перевіряє: у `dependencies` не повинно бути `ioredis`, `node-redis`,
|
|
9
|
-
# `redis` або жодного з підпакетів `@redis/*` — заміна на Bun native Redis
|
|
10
|
-
# (https://bun.com/docs/runtime/redis).
|
|
11
|
-
#
|
|
12
|
-
# AST-скан коду (`import` / `require` / dynamic `import()` тих самих пакетів)
|
|
13
|
-
# лишається у `npm/scripts/check-js-bun-redis.mjs` (потребує `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).
|
|
3
|
+
# Канон надходить через --data: { "template": { "deny": ... } }
|
|
4
|
+
# Структура --data сформована з template/package.json.deny.json.
|
|
5
|
+
# AST-скан коду (`import`/`require`/dynamic `import()` тих самих пакетів)
|
|
6
|
+
# лишається у `js-bun-redis/fix/imports/check.mjs`.
|
|
18
7
|
package js_bun_redis.package_json
|
|
19
8
|
|
|
20
9
|
import rego.v1
|
|
21
10
|
|
|
22
|
-
forbidden_dependencies := {
|
|
23
|
-
"ioredis",
|
|
24
|
-
"node-redis",
|
|
25
|
-
"redis",
|
|
26
|
-
"@redis/client",
|
|
27
|
-
"@redis/json",
|
|
28
|
-
"@redis/search",
|
|
29
|
-
"@redis/time-series",
|
|
30
|
-
"@redis/bloom",
|
|
31
|
-
}
|
|
32
|
-
|
|
33
11
|
deny contains msg if {
|
|
34
|
-
some
|
|
35
|
-
|
|
36
|
-
msg := sprintf("dependencies
|
|
12
|
+
some pkg, reason in data.template.deny.dependencies
|
|
13
|
+
pkg in object.keys(object.get(input, "dependencies", {}))
|
|
14
|
+
msg := sprintf("dependencies.%s — %s", [pkg, reason])
|
|
37
15
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"dependencies": {
|
|
3
|
+
"ioredis": "заміни на Bun native Redis (js-bun-redis.mdc)",
|
|
4
|
+
"node-redis": "заміни на Bun native Redis (js-bun-redis.mdc)",
|
|
5
|
+
"redis": "заміни на Bun native Redis (js-bun-redis.mdc)",
|
|
6
|
+
"@redis/client": "заміни на Bun native Redis (js-bun-redis.mdc)",
|
|
7
|
+
"@redis/json": "заміни на Bun native Redis (js-bun-redis.mdc)",
|
|
8
|
+
"@redis/search": "заміни на Bun native Redis (js-bun-redis.mdc)",
|
|
9
|
+
"@redis/time-series": "заміни на Bun native Redis (js-bun-redis.mdc)",
|
|
10
|
+
"@redis/bloom": "заміни на Bun native Redis (js-bun-redis.mdc)"
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -1,38 +1,44 @@
|
|
|
1
|
-
# Перевірка `.jscpd.json`
|
|
1
|
+
# Перевірка `.jscpd.json` (js-lint.mdc).
|
|
2
2
|
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
|
|
3
|
+
# Канон надходить через --data: { "template": { "snippet": ... } }
|
|
4
|
+
# Структура --data сформована з template/.jscpd.json.snippet.json.
|
|
5
|
+
# Особливості:
|
|
6
|
+
# - `reporters` — subset-of (масив string)
|
|
7
|
+
# - `minLines` — >= expected (semantic: дозволяється більше, не менше)
|
|
8
|
+
# - `gitignore`/`exitCode` — exact match
|
|
10
9
|
package js_lint.jscpd
|
|
11
10
|
|
|
12
11
|
import rego.v1
|
|
13
12
|
|
|
13
|
+
# Top-level scalar leafs (exact match) для gitignore + exitCode.
|
|
14
14
|
deny contains msg if {
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
some key, expected_value in data.template.snippet
|
|
16
|
+
key != "minLines"
|
|
17
|
+
not is_array(expected_value)
|
|
18
|
+
actual := object.get(input, key, null)
|
|
19
|
+
actual != expected_value
|
|
20
|
+
msg := sprintf(".jscpd.json має містити \"%s\": %v (js-lint.mdc)", [key, expected_value])
|
|
17
21
|
}
|
|
18
22
|
|
|
23
|
+
# Array subset-of для reporters.
|
|
19
24
|
deny contains msg if {
|
|
20
|
-
|
|
21
|
-
|
|
25
|
+
some field, expected_values in data.template.snippet
|
|
26
|
+
is_array(expected_values)
|
|
27
|
+
actual_set := {v | some v in object.get(input, field, [])}
|
|
28
|
+
some required in expected_values
|
|
29
|
+
not required in actual_set
|
|
30
|
+
msg := sprintf(".jscpd.json має містити \"%s\": [\"%s\"] (js-lint.mdc)", [field, required])
|
|
22
31
|
}
|
|
23
32
|
|
|
33
|
+
# minLines: must be number and >= expected.
|
|
24
34
|
deny contains msg if {
|
|
25
|
-
|
|
26
|
-
|
|
35
|
+
expected := data.template.snippet.minLines
|
|
36
|
+
actual := object.get(input, "minLines", null)
|
|
37
|
+
not is_valid_min_lines(actual, expected)
|
|
38
|
+
msg := sprintf(".jscpd.json має містити \"minLines\" як число >= %d (js-lint.mdc)", [expected])
|
|
27
39
|
}
|
|
28
40
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
deny contains msg if {
|
|
35
|
-
is_number(input.minLines)
|
|
36
|
-
input.minLines < 25
|
|
37
|
-
msg := ".jscpd.json має містити \"minLines\" як число >= 25 (js-lint.mdc)"
|
|
41
|
+
is_valid_min_lines(actual, expected) if {
|
|
42
|
+
is_number(actual)
|
|
43
|
+
actual >= expected
|
|
38
44
|
}
|
|
@@ -1,27 +1,35 @@
|
|
|
1
|
-
#
|
|
2
|
-
# (js-lint.mdc) — структурні очікування `verifyLintJsWorkflowStructure`.
|
|
1
|
+
# Перевірка `.github/workflows/lint-js.yml` (js-lint.mdc).
|
|
3
2
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
3
|
+
# Канон надходить через --data: { "template": { "snippet": ... } }
|
|
4
|
+
# Структура --data сформована з template/lint-js.yml.snippet.yml.
|
|
5
|
+
# Required uses + run substrings зчитуються з template's eslint-job steps.
|
|
6
|
+
# Універсальні workflow-перевірки — у `ga.workflow_common`.
|
|
7
7
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
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).
|
|
8
|
+
# Логіка, що ЛИШАЄТЬСЯ у rego (inverse anti-patterns):
|
|
9
|
+
# - `oxlint --fix` / `eslint --fix` у CI заборонено;
|
|
10
|
+
# - actions/checkout@v6 має мати `with.persist-credentials: false`.
|
|
20
11
|
package js_lint.lint_js_yml
|
|
21
12
|
|
|
22
13
|
import rego.v1
|
|
23
14
|
|
|
24
|
-
#
|
|
15
|
+
# Required `uses:` зі template — фільтруємо тільки кроки з uses.
|
|
16
|
+
expected_uses_set contains u if {
|
|
17
|
+
some step in data.template.snippet.jobs.eslint.steps
|
|
18
|
+
u := object.get(step, "uses", "")
|
|
19
|
+
u != ""
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
# Required `run:` substrings (per-line з template).
|
|
23
|
+
expected_run_substrings contains line if {
|
|
24
|
+
some step in data.template.snippet.jobs.eslint.steps
|
|
25
|
+
r := object.get(step, "run", "")
|
|
26
|
+
r != ""
|
|
27
|
+
some raw in split(r, "\n")
|
|
28
|
+
line := trim_space(raw)
|
|
29
|
+
line != ""
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# Аліаси на input.
|
|
25
33
|
all_steps contains step if {
|
|
26
34
|
some job in object.get(input, "jobs", {})
|
|
27
35
|
some step in object.get(job, "steps", [])
|
|
@@ -37,46 +45,30 @@ all_run_blob := concat("\n", [r |
|
|
|
37
45
|
r := step_run_text(step)
|
|
38
46
|
])
|
|
39
47
|
|
|
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
|
-
}
|
|
48
|
+
# ── deny: required uses ─────────────────────────────────────────────────
|
|
51
49
|
|
|
52
50
|
deny contains msg if {
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
some required_use in expected_uses_set
|
|
52
|
+
not contains(all_uses_blob, required_use)
|
|
53
|
+
msg := sprintf("lint-js.yml: відсутній крок uses: %s (js-lint.mdc)", [required_use])
|
|
55
54
|
}
|
|
56
55
|
|
|
57
|
-
# ── deny: required run substrings
|
|
56
|
+
# ── deny: required run substrings ───────────────────────────────────────
|
|
58
57
|
|
|
59
58
|
deny contains msg if {
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
some required_run in expected_run_substrings
|
|
60
|
+
not contains(all_run_blob, required_run)
|
|
61
|
+
msg := sprintf("lint-js.yml: у run немає %q (js-lint.mdc)", [required_run])
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
deny
|
|
65
|
-
not contains(all_run_blob, "bunx eslint .")
|
|
66
|
-
msg := "lint-js.yml: у run немає `bunx eslint .` (js-lint.mdc)"
|
|
67
|
-
}
|
|
64
|
+
# ── deny: actions/checkout@v6 has persist-credentials: false (inverse) ──
|
|
68
65
|
|
|
69
66
|
deny contains msg if {
|
|
70
|
-
not
|
|
71
|
-
msg := "lint-js.yml:
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
deny contains msg if {
|
|
75
|
-
not contains(all_run_blob, "bunx knip")
|
|
76
|
-
msg := "lint-js.yml: у run немає `bunx knip` (js-lint.mdc)"
|
|
67
|
+
not has_checkout_persist_credentials_false
|
|
68
|
+
msg := "lint-js.yml: actions/checkout@v6 має бути з with.persist-credentials: false (js-lint.mdc)"
|
|
77
69
|
}
|
|
78
70
|
|
|
79
|
-
# ── deny: --fix у CI заборонено
|
|
71
|
+
# ── deny: --fix у CI заборонено (inverse) ───────────────────────────────
|
|
80
72
|
|
|
81
73
|
deny contains msg if {
|
|
82
74
|
regex.match(`bunx\s+oxlint[^\n]*--fix`, all_run_blob)
|
|
@@ -88,7 +80,7 @@ deny contains msg if {
|
|
|
88
80
|
msg := "lint-js.yml: у run є `eslint --fix` (у CI заборонено) (js-lint.mdc)"
|
|
89
81
|
}
|
|
90
82
|
|
|
91
|
-
# ── helpers
|
|
83
|
+
# ── helpers ─────────────────────────────────────────────────────────────
|
|
92
84
|
|
|
93
85
|
has_checkout_persist_credentials_false if {
|
|
94
86
|
some step in all_steps
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: Lint JS
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- dev
|
|
7
|
+
- main
|
|
8
|
+
paths:
|
|
9
|
+
- '**/*.js'
|
|
10
|
+
- '**/*.mjs'
|
|
11
|
+
- '**/*.cjs'
|
|
12
|
+
- '**/*.jsx'
|
|
13
|
+
- '**/*.ts'
|
|
14
|
+
- '**/*.tsx'
|
|
15
|
+
- '**/*.vue'
|
|
16
|
+
- '**/eslint.config.*'
|
|
17
|
+
|
|
18
|
+
pull_request:
|
|
19
|
+
branches:
|
|
20
|
+
- dev
|
|
21
|
+
- main
|
|
22
|
+
|
|
23
|
+
concurrency:
|
|
24
|
+
group: ${{ github.ref }}-${{ github.workflow }}
|
|
25
|
+
cancel-in-progress: true
|
|
26
|
+
|
|
27
|
+
jobs:
|
|
28
|
+
eslint:
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
permissions:
|
|
31
|
+
contents: read
|
|
32
|
+
steps:
|
|
33
|
+
- uses: actions/checkout@v6
|
|
34
|
+
with:
|
|
35
|
+
persist-credentials: false
|
|
36
|
+
|
|
37
|
+
- uses: ./.github/actions/setup-bun-deps
|
|
38
|
+
|
|
39
|
+
- name: Eslint
|
|
40
|
+
run: |
|
|
41
|
+
bunx oxlint
|
|
42
|
+
bunx eslint .
|
|
43
|
+
bunx jscpd .
|
|
44
|
+
bunx knip --no-config-hints
|
|
@@ -1,47 +1,36 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Перевірка `package.json` (js-lint.mdc).
|
|
2
2
|
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
3
|
+
# Канон надходить через --data: { "template": { "snippet": ... } }
|
|
4
|
+
# Структура --data сформована з template/package.json.snippet.json
|
|
5
|
+
# (canonical `type` + `scripts.lint-js`).
|
|
5
6
|
#
|
|
6
|
-
#
|
|
7
|
-
# `
|
|
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).
|
|
7
|
+
# Логіка, що ЛИШАЄТЬСЯ у rego (inverse, не виноситься у template):
|
|
8
|
+
# - `engines.node` >= 24, `engines.bun` >= 1.3 (semver range parsing);
|
|
9
|
+
# - `@nitra/eslint-config` >= 3.9.2 (semver range parsing).
|
|
16
10
|
package js_lint.package_json
|
|
17
11
|
|
|
18
12
|
import rego.v1
|
|
19
13
|
|
|
20
|
-
|
|
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
|
-
}
|
|
14
|
+
# ── deny: top-level scalar leafs (type) ─────────────────────────────────
|
|
29
15
|
|
|
30
16
|
deny contains msg if {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
17
|
+
some key, expected_value in data.template.snippet
|
|
18
|
+
not is_object(expected_value)
|
|
19
|
+
actual := object.get(input, key, null)
|
|
20
|
+
actual != expected_value
|
|
21
|
+
msg := sprintf("package.json: \"%s\" має бути %q (js-lint.mdc)", [key, expected_value])
|
|
35
22
|
}
|
|
36
23
|
|
|
37
|
-
# ── deny:
|
|
24
|
+
# ── deny: scripts (nested) — exact match із normalize ──────────────────
|
|
38
25
|
|
|
39
26
|
deny contains msg if {
|
|
40
|
-
|
|
41
|
-
|
|
27
|
+
some script_name, expected in data.template.snippet.scripts
|
|
28
|
+
actual := object.get(object.get(input, "scripts", {}), script_name, "")
|
|
29
|
+
normalize_script(actual) != expected
|
|
30
|
+
msg := sprintf("package.json: scripts.%s має бути %q (js-lint.mdc)", [script_name, expected])
|
|
42
31
|
}
|
|
43
32
|
|
|
44
|
-
# ── deny: engines
|
|
33
|
+
# ── deny: engines.node >= 24 (inverse, у rego) ──────────────────────────
|
|
45
34
|
|
|
46
35
|
deny contains msg if {
|
|
47
36
|
engines := object.get(input, "engines", {})
|
|
@@ -55,7 +44,7 @@ deny contains msg if {
|
|
|
55
44
|
msg := "package.json: engines.bun має бути >= 1.3 (js-lint.mdc)"
|
|
56
45
|
}
|
|
57
46
|
|
|
58
|
-
# ── deny: @nitra/eslint-config
|
|
47
|
+
# ── deny: @nitra/eslint-config >= 3.9.2 (inverse) ───────────────────────
|
|
59
48
|
|
|
60
49
|
deny contains msg if {
|
|
61
50
|
dev := object.get(input, "devDependencies", {})
|
|
@@ -70,20 +59,15 @@ deny contains msg if {
|
|
|
70
59
|
msg := sprintf("package.json: @nitra/eslint-config має бути >= 3.9.2 (зараз %q) (js-lint.mdc)", [range])
|
|
71
60
|
}
|
|
72
61
|
|
|
73
|
-
# ── helpers
|
|
62
|
+
# ── helpers ──────────────────────────────────────────────────────────────
|
|
74
63
|
|
|
75
|
-
|
|
76
|
-
normalize_lint_js(s) := regex.replace(trim_space(s), `\s+`, " ")
|
|
64
|
+
normalize_script(s) := regex.replace(trim_space(s), `\s+`, " ")
|
|
77
65
|
|
|
78
|
-
# `engines.node`: дозволяється `>=24`, `^24`, `24.x`, `24.0.0` тощо. Дістаємо
|
|
79
|
-
# першу мажорну цифру; вона має бути ≥ 24.
|
|
80
66
|
engines_node_meets(spec) if {
|
|
81
67
|
major := first_major(spec)
|
|
82
68
|
major >= 24
|
|
83
69
|
}
|
|
84
70
|
|
|
85
|
-
# `engines.bun`: дозволяється `>=1.3`, `^1.3.0`, `1.3.x` тощо. Перша мажор-мінор
|
|
86
|
-
# пара має бути ≥ 1.3.
|
|
87
71
|
engines_bun_meets(spec) if {
|
|
88
72
|
parts := split_to_numbers(spec)
|
|
89
73
|
count(parts) >= 2
|
|
@@ -97,7 +81,6 @@ engines_bun_meets(spec) if {
|
|
|
97
81
|
parts[1] >= 3
|
|
98
82
|
}
|
|
99
83
|
|
|
100
|
-
# `@nitra/eslint-config`: ≥ 3.9.2; `workspace:*` теж OK.
|
|
101
84
|
eslint_config_meets_min(range) if startswith(trim_space(range), "workspace:")
|
|
102
85
|
|
|
103
86
|
eslint_config_meets_min(range) if {
|
|
@@ -121,15 +104,12 @@ eslint_config_meets_min(range) if {
|
|
|
121
104
|
parts[2] >= 2
|
|
122
105
|
}
|
|
123
106
|
|
|
124
|
-
# Перша мажорна цифра з рядка-діапазону (наприклад `^24.1.0` → 24).
|
|
125
107
|
first_major(spec) := major if {
|
|
126
108
|
parts := split_to_numbers(spec)
|
|
127
109
|
count(parts) >= 1
|
|
128
110
|
major := parts[0]
|
|
129
111
|
}
|
|
130
112
|
|
|
131
|
-
# Розкидати рядок версії на список чисел (відкидаючи range-оператори і нечислові
|
|
132
|
-
# фрагменти). `^24.1.0` → [24, 1, 0]; `>=1.3` → [1, 3]; `workspace:*` → [].
|
|
133
113
|
split_to_numbers(spec) := nums if {
|
|
134
114
|
tokens := regex.split(`\D+`, spec)
|
|
135
115
|
non_empty := [t | some t in tokens; t != ""]
|