@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,25 +1,12 @@
|
|
|
1
1
|
# Перевірка `.vscode/extensions.json` для js-lint (js-lint.mdc).
|
|
2
2
|
#
|
|
3
|
-
#
|
|
4
|
-
# Канон задає мінімум — додаткові рекомендації від інших правил дозволені.
|
|
5
|
-
#
|
|
6
|
-
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
7
|
-
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
|
|
8
|
-
# (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
|
|
3
|
+
# Канон надходить через --data: { "template": { "snippet": ... } }
|
|
9
4
|
package js_lint.vscode_extensions
|
|
10
5
|
|
|
11
6
|
import rego.v1
|
|
12
7
|
|
|
13
|
-
required_extensions := {
|
|
14
|
-
"dbaeumer.vscode-eslint",
|
|
15
|
-
"github.vscode-github-actions",
|
|
16
|
-
"oxc.oxc-vscode",
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
recommendations_set := {r | some r in object.get(input, "recommendations", [])}
|
|
20
|
-
|
|
21
8
|
deny contains msg if {
|
|
22
|
-
some
|
|
23
|
-
not
|
|
24
|
-
msg := sprintf(".vscode/extensions.json: recommendations має містити %q (js-lint.mdc)", [
|
|
9
|
+
some rec in data.template.snippet.recommendations
|
|
10
|
+
not rec in {r | some r in object.get(input, "recommendations", [])}
|
|
11
|
+
msg := sprintf(".vscode/extensions.json: recommendations має містити %q (js-lint.mdc)", [rec])
|
|
25
12
|
}
|
|
@@ -1,45 +1,21 @@
|
|
|
1
|
-
#
|
|
2
|
-
# (js-run.mdc) — `OTEL_RESOURCE_ATTRIBUTES` має містити `service.name=` і
|
|
3
|
-
# `service.namespace=`.
|
|
1
|
+
# Перевірка ConfigMap (js-run.mdc).
|
|
4
2
|
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# Відповідність імені ConfigMap імені Deployment (cross-file) — у JS і `check-k8s.mjs`.
|
|
10
|
-
#
|
|
11
|
-
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
12
|
-
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
|
|
13
|
-
# (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
|
|
3
|
+
# Канон надходить через --data: { "template": { "contains": ... } }
|
|
4
|
+
# Структура --data сформована з template/configmap.yaml.contains.yml.
|
|
5
|
+
# Контекст: kind=ConfigMap; OTEL_RESOURCE_ATTRIBUTES має містити кожен substring
|
|
6
|
+
# зі snippet (`service.name=`, `service.namespace=`).
|
|
14
7
|
package js_run.configmap
|
|
15
8
|
|
|
16
9
|
import rego.v1
|
|
17
10
|
|
|
18
|
-
# Шаблони повідомлень — через `concat` для regal style/line-length.
|
|
19
|
-
otel_service_name_template := concat(" ", [
|
|
20
|
-
"ConfigMap %q: OTEL_RESOURCE_ATTRIBUTES має містити",
|
|
21
|
-
"`service.name=` (js-run.mdc)",
|
|
22
|
-
])
|
|
23
|
-
|
|
24
|
-
otel_service_namespace_template := concat(" ", [
|
|
25
|
-
"ConfigMap %q: OTEL_RESOURCE_ATTRIBUTES має містити",
|
|
26
|
-
"`service.namespace=` (js-run.mdc)",
|
|
27
|
-
])
|
|
28
|
-
|
|
29
|
-
deny contains msg if {
|
|
30
|
-
input.kind == "ConfigMap"
|
|
31
|
-
otel := object.get(object.get(input, "data", {}), "OTEL_RESOURCE_ATTRIBUTES", "")
|
|
32
|
-
otel != ""
|
|
33
|
-
not contains(otel, "service.name=")
|
|
34
|
-
msg := sprintf(otel_service_name_template, [cm_name])
|
|
35
|
-
}
|
|
36
|
-
|
|
37
11
|
deny contains msg if {
|
|
38
12
|
input.kind == "ConfigMap"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
13
|
+
some field, needles in data.template.contains.data
|
|
14
|
+
actual := object.get(object.get(input, "data", {}), field, "")
|
|
15
|
+
actual != ""
|
|
16
|
+
some needle in needles
|
|
17
|
+
not contains(actual, needle)
|
|
18
|
+
msg := sprintf("ConfigMap %q: %s має містити %q (js-run.mdc)", [cm_name, field, needle])
|
|
43
19
|
}
|
|
44
20
|
|
|
45
21
|
cm_name := object.get(object.get(input, "metadata", {}), "name", "?")
|
|
@@ -1,66 +1,59 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Перевірка `jsconfig.json` (js-run.mdc).
|
|
2
2
|
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# Перевіряє: `compilerOptions.{lib, module, moduleResolution, target, checkJs}` і
|
|
8
|
-
# `include` мають канонічні значення (js-run.mdc).
|
|
9
|
-
#
|
|
10
|
-
# FS-перевірка (наявність каталогу `src/` у пакеті, наявність самого `jsconfig.json`)
|
|
11
|
-
# і вибір файлу-кандидата — у JS.
|
|
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).
|
|
3
|
+
# Канон надходить через --data: { "template": { "snippet": ... } }
|
|
4
|
+
# Структура --data сформована з template/jsconfig.json.snippet.yml.
|
|
5
|
+
# Walker: для кожного leaf у template's snippet порівнюємо input[path].
|
|
6
|
+
# Для масивів — equality (не subset-of, бо в jsconfig потрібен точний набір).
|
|
16
7
|
package js_run.jsconfig
|
|
17
8
|
|
|
18
9
|
import rego.v1
|
|
19
10
|
|
|
20
|
-
#
|
|
21
|
-
|
|
11
|
+
# Generic 2-level walker: section → key → expected (для compilerOptions).
|
|
22
12
|
deny contains msg if {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
13
|
+
some section, expected_inner in data.template.snippet
|
|
14
|
+
is_object(expected_inner)
|
|
15
|
+
inner := object.get(input, section, {})
|
|
16
|
+
is_object(inner)
|
|
17
|
+
some leaf_key, expected_value in expected_inner
|
|
18
|
+
actual := object.get(inner, leaf_key, null)
|
|
19
|
+
not values_match(actual, expected_value)
|
|
20
|
+
msg := sprintf("jsconfig.json: %s.%s має бути %v (js-run.mdc)", [section, leaf_key, expected_value])
|
|
26
21
|
}
|
|
27
22
|
|
|
23
|
+
# Section відсутня або не обʼєкт.
|
|
28
24
|
deny contains msg if {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
some section, expected_inner in data.template.snippet
|
|
26
|
+
is_object(expected_inner)
|
|
27
|
+
raw := object.get(input, section, null)
|
|
28
|
+
not is_object(raw)
|
|
29
|
+
msg := sprintf("jsconfig.json: відсутній обʼєкт %s (js-run.mdc)", [section])
|
|
33
30
|
}
|
|
34
31
|
|
|
32
|
+
# Top-level масив (наприклад include) — порівнюємо як множину.
|
|
35
33
|
deny contains msg if {
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
some field, expected_array in data.template.snippet
|
|
35
|
+
is_array(expected_array)
|
|
36
|
+
actual := object.get(input, field, null)
|
|
37
|
+
not is_array(actual)
|
|
38
|
+
msg := sprintf("jsconfig.json: %s має бути масив %v (js-run.mdc)", [field, expected_array])
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
deny contains msg if {
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
some field, expected_array in data.template.snippet
|
|
43
|
+
is_array(expected_array)
|
|
44
|
+
is_array(object.get(input, field, null))
|
|
45
|
+
{x | some x in input[field]} != {x | some x in expected_array}
|
|
46
|
+
msg := sprintf("jsconfig.json: %s має бути %v (js-run.mdc)", [field, expected_array])
|
|
43
47
|
}
|
|
44
48
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
# Helper: leaf-level value match, з підтримкою масивів як множин.
|
|
50
|
+
values_match(actual, expected) if {
|
|
51
|
+
not is_array(expected)
|
|
52
|
+
actual == expected
|
|
48
53
|
}
|
|
49
54
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
# ── deny: include ──────────────────────────────────────────────────────────
|
|
56
|
-
|
|
57
|
-
deny contains msg if {
|
|
58
|
-
not is_array(object.get(input, "include", null))
|
|
59
|
-
msg := "jsconfig.json: include має бути [\"src/**/*\"] (js-run.mdc)"
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
deny contains msg if {
|
|
63
|
-
is_array(input.include)
|
|
64
|
-
{p | some p in input.include} != {"src/**/*"}
|
|
65
|
-
msg := "jsconfig.json: include має бути [\"src/**/*\"] (js-run.mdc)"
|
|
55
|
+
values_match(actual, expected) if {
|
|
56
|
+
is_array(expected)
|
|
57
|
+
is_array(actual)
|
|
58
|
+
{x | some x in actual} == {x | some x in expected}
|
|
66
59
|
}
|
|
@@ -1,31 +1,20 @@
|
|
|
1
|
-
#
|
|
2
|
-
# (js-run.mdc) — заборона `bunyan` / `@nitra/bunyan`.
|
|
1
|
+
# Перевірка `package.json` (js-run.mdc).
|
|
3
2
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# AST-скан коду на імпорти `bunyan` / `process.env` без `checkEnv`,
|
|
9
|
-
# `new Promise(resolve => setTimeout(resolve, ...))`, обмеження `#conn/*`-аліасів —
|
|
10
|
-
# у JS (потребує парсингу `.js` / `.ts` через oxc-parser).
|
|
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).
|
|
3
|
+
# Канон надходить через --data: { "template": { "deny": ... } }
|
|
4
|
+
# Структура --data сформована з template/package.json.deny.json.
|
|
5
|
+
# AST-скан коду (`bunyan`/`process.env`/`#conn/*`) — у JS.
|
|
15
6
|
package js_run.package_json
|
|
16
7
|
|
|
17
8
|
import rego.v1
|
|
18
9
|
|
|
19
|
-
forbidden_packages := {"bunyan", "@nitra/bunyan"}
|
|
20
|
-
|
|
21
10
|
deny contains msg if {
|
|
22
|
-
some
|
|
23
|
-
|
|
24
|
-
msg := sprintf("dependencies
|
|
11
|
+
some pkg, reason in data.template.deny.dependencies
|
|
12
|
+
pkg in object.keys(object.get(input, "dependencies", {}))
|
|
13
|
+
msg := sprintf("dependencies.%s — %s", [pkg, reason])
|
|
25
14
|
}
|
|
26
15
|
|
|
27
16
|
deny contains msg if {
|
|
28
|
-
some
|
|
29
|
-
|
|
30
|
-
msg := sprintf("devDependencies
|
|
17
|
+
some pkg, reason in data.template.deny.devDependencies
|
|
18
|
+
pkg in object.keys(object.get(input, "devDependencies", {}))
|
|
19
|
+
msg := sprintf("devDependencies.%s — %s", [pkg, reason])
|
|
31
20
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"dependencies": {
|
|
3
|
+
"bunyan": "використовуй стандартні логери (js-run.mdc)",
|
|
4
|
+
"@nitra/bunyan": "використовуй стандартні логери (js-run.mdc)"
|
|
5
|
+
},
|
|
6
|
+
"devDependencies": {
|
|
7
|
+
"bunyan": "використовуй стандартні логери (js-run.mdc)",
|
|
8
|
+
"@nitra/bunyan": "використовуй стандартні логери (js-run.mdc)"
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -75,39 +75,10 @@ bunx -p typescript tsc src/**/*.js --declaration --allowJs --emitDeclarationOnly
|
|
|
75
75
|
|
|
76
76
|
**`npm-publish.yml`:** push у **`main`**, **`on.push.paths`** з **`npm/**`**, **`JS-DevTools/npm-publish@v4.1.5`**, **`with.package: npm/package.json`**, **`permissions.id-token: write`** (OIDC на npm).
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
branches:
|
|
86
|
-
- main
|
|
87
|
-
|
|
88
|
-
concurrency:
|
|
89
|
-
group: ${{ github.ref }}-${{ github.workflow }}
|
|
90
|
-
cancel-in-progress: true
|
|
91
|
-
|
|
92
|
-
jobs:
|
|
93
|
-
publish:
|
|
94
|
-
runs-on: ubuntu-latest
|
|
95
|
-
permissions:
|
|
96
|
-
contents: read
|
|
97
|
-
id-token: write # КРИТИЧНО для OIDC!
|
|
98
|
-
|
|
99
|
-
steps:
|
|
100
|
-
- uses: actions/checkout@v6
|
|
101
|
-
with:
|
|
102
|
-
persist-credentials: false
|
|
103
|
-
|
|
104
|
-
- uses: actions/setup-node@v6
|
|
105
|
-
with:
|
|
106
|
-
node-version: '24' # includes npm@11.6.0
|
|
107
|
-
registry-url: 'https://registry.npmjs.org'
|
|
108
|
-
|
|
109
|
-
- name: Publish package
|
|
110
|
-
uses: JS-DevTools/npm-publish@v4.1.5
|
|
111
|
-
with:
|
|
112
|
-
package: npm/package.json
|
|
113
|
-
```
|
|
78
|
+
- Канон: [npm-publish.yml.snippet.yml](./policy/npm_publish_yml/template/npm-publish.yml.snippet.yml)
|
|
79
|
+
|
|
80
|
+
## Канонічні конфіги
|
|
81
|
+
|
|
82
|
+
- Кореневий `package.json` (workspaces): [package.json.snippet.json](./policy/root_package_json/template/package.json.snippet.json)
|
|
83
|
+
- `npm/package.json` (whitelist `files` обовʼязково має містити `types`): [package.json.snippet.json](./policy/npm_package_json/template/package.json.snippet.json)
|
|
84
|
+
- `npm/tsconfig.emit-types.json` (canonical `compilerOptions` для emit-types): [tsconfig.emit-types.json.snippet.json](./policy/emit_types_config/template/tsconfig.emit-types.json.snippet.json)
|
|
@@ -1,37 +1,28 @@
|
|
|
1
|
-
#
|
|
2
|
-
# (npm-module.mdc).
|
|
1
|
+
# Перевірка `npm/tsconfig.emit-types.json` (npm-module.mdc).
|
|
3
2
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# Перевіряє: `compilerOptions.{allowJs, declaration, emitDeclarationOnly, outDir,
|
|
9
|
-
# skipLibCheck}` мають канонічні значення (true/true/true/"types"/true). FS-перевірки
|
|
10
|
-
# (наявність самого `tsconfig.emit-types.json`, активність layout-варіанта) — у 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).
|
|
3
|
+
# Канон надходить через --data: { "template": { "snippet": ... } }
|
|
4
|
+
# Структура --data сформована з template/tsconfig.emit-types.json.snippet.json.
|
|
5
|
+
# Snippet — 2-рівнева мапа (section → key → expected). Walker такий самий,
|
|
6
|
+
# як для ga.vscode_settings / bun.bunfig.
|
|
15
7
|
package npm_module.emit_types_config
|
|
16
8
|
|
|
17
9
|
import rego.v1
|
|
18
10
|
|
|
19
|
-
|
|
20
|
-
"allowJs": true,
|
|
21
|
-
"declaration": true,
|
|
22
|
-
"emitDeclarationOnly": true,
|
|
23
|
-
"outDir": "types",
|
|
24
|
-
"skipLibCheck": true,
|
|
25
|
-
}
|
|
26
|
-
|
|
11
|
+
# Leaf-by-leaf: коли section присутня й обʼєкт.
|
|
27
12
|
deny contains msg if {
|
|
28
|
-
|
|
29
|
-
|
|
13
|
+
some section, expected_inner in data.template.snippet
|
|
14
|
+
inner := object.get(input, section, {})
|
|
15
|
+
is_object(inner)
|
|
16
|
+
some leaf_key, expected_value in expected_inner
|
|
17
|
+
actual := object.get(inner, leaf_key, null)
|
|
18
|
+
actual != expected_value
|
|
19
|
+
msg := sprintf("npm/tsconfig.emit-types.json: %s.%s має бути %v (npm-module.mdc)", [section, leaf_key, expected_value])
|
|
30
20
|
}
|
|
31
21
|
|
|
22
|
+
# Section відсутня (null) або не обʼєкт.
|
|
32
23
|
deny contains msg if {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
msg := sprintf("npm/tsconfig.emit-types.json:
|
|
24
|
+
some section in object.keys(data.template.snippet)
|
|
25
|
+
raw := object.get(input, section, null)
|
|
26
|
+
not is_object(raw)
|
|
27
|
+
msg := sprintf("npm/tsconfig.emit-types.json: відсутній %s (npm-module.mdc)", [section])
|
|
37
28
|
}
|
|
@@ -1,44 +1,29 @@
|
|
|
1
|
-
# Порт перевірок `npm/package.json`
|
|
2
|
-
# (npm-module.mdc).
|
|
1
|
+
# Порт перевірок `npm/package.json` (npm-module.mdc).
|
|
3
2
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
3
|
+
# Канон надходить через --data: { "template": { "snippet": ... } }
|
|
4
|
+
# Структура --data сформована з template/package.json.snippet.json
|
|
5
|
+
# (snippet-array subset-of для whitelist `files`).
|
|
7
6
|
#
|
|
8
|
-
#
|
|
9
|
-
# -
|
|
10
|
-
#
|
|
11
|
-
# - масив `files` присутній, непорожній і містить `"types"` (whitelist
|
|
12
|
-
# обовʼязковий — без нього npm пакує майже все);
|
|
13
|
-
# - `devDependencies` відсутні або порожні: dev-інструментарій тримаємо у
|
|
14
|
-
# кореневому `package.json` монорепо, щоб `npm install @nitra/<pkg>` його
|
|
15
|
-
# не тягнув (npm-module.mdc: компактний пакет).
|
|
7
|
+
# Логіка, що ЛИШАЄТЬСЯ у rego (inverse-patterns, не виносяться у template):
|
|
8
|
+
# - форма поля `types` (regex pattern: `./types/index.d.ts` або `./types/<…>.d.ts|.d.mts`);
|
|
9
|
+
# - `devDependencies` мають бути відсутні або порожні (inverse-pattern — заборона будь-яких).
|
|
16
10
|
#
|
|
17
|
-
#
|
|
18
|
-
# існування файлу зі шляху `types` і скан тест-патернів у tarball — у
|
|
19
|
-
# JS-перевірці (`check-npm-module.mjs`: cross-file / FS-access / AST).
|
|
20
|
-
#
|
|
21
|
-
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
22
|
-
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
|
|
23
|
-
# (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
|
|
11
|
+
# FS-перевірки (наявність файлу зі шляху `types`, скан tarball на тест-патерни) — у JS.
|
|
24
12
|
package npm_module.npm_package_json
|
|
25
13
|
|
|
26
14
|
import rego.v1
|
|
27
15
|
|
|
28
|
-
# Шаблон повідомлення про неканонічне поле `types` — через `concat` для
|
|
29
|
-
# regal style/line-length.
|
|
30
16
|
types_field_template := concat(" ", [
|
|
31
17
|
"npm/package.json: поле \"types\" має бути \"./types/index.d.ts\"",
|
|
32
18
|
"або \"./types/<…>.d.ts|.d.mts\" (зараз: %v) (npm-module.mdc)",
|
|
33
19
|
])
|
|
34
20
|
|
|
35
|
-
# Шаблон повідомлення про присутність `devDependencies`.
|
|
36
21
|
dev_deps_template := concat(" ", [
|
|
37
22
|
"npm/package.json: \"devDependencies\" не публікуються користувачам пакета —",
|
|
38
23
|
"перенеси у кореневий package.json: %v (npm-module.mdc: компактний пакет)",
|
|
39
24
|
])
|
|
40
25
|
|
|
41
|
-
# ── deny: types
|
|
26
|
+
# ── deny: types (regex — лишається в rego) ───────────────────────────────
|
|
42
27
|
|
|
43
28
|
deny contains msg if {
|
|
44
29
|
types_field := object.get(input, "types", "")
|
|
@@ -46,7 +31,7 @@ deny contains msg if {
|
|
|
46
31
|
msg := sprintf(types_field_template, [types_field])
|
|
47
32
|
}
|
|
48
33
|
|
|
49
|
-
# ── deny: files має
|
|
34
|
+
# ── deny: files має існувати та бути масивом ─────────────────────────────
|
|
50
35
|
|
|
51
36
|
deny contains msg if {
|
|
52
37
|
not is_array(object.get(input, "files", null))
|
|
@@ -59,14 +44,19 @@ deny contains msg if {
|
|
|
59
44
|
msg := "npm/package.json: масив \"files\" не повинен бути порожнім (npm-module.mdc: компактний пакет)"
|
|
60
45
|
}
|
|
61
46
|
|
|
47
|
+
# ── deny: files subset-of з template (template-driven) ──────────────────
|
|
48
|
+
|
|
62
49
|
deny contains msg if {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
50
|
+
some field, expected_values in data.template.snippet
|
|
51
|
+
is_array(object.get(input, field, null))
|
|
52
|
+
count(input[field]) > 0
|
|
53
|
+
actual_set := {v | some v in input[field]}
|
|
54
|
+
some required in expected_values
|
|
55
|
+
not required in actual_set
|
|
56
|
+
msg := sprintf("npm/package.json: масив \"%s\" має містити %q (npm-module.mdc)", [field, required])
|
|
67
57
|
}
|
|
68
58
|
|
|
69
|
-
# ── deny:
|
|
59
|
+
# ── deny: devDependencies (inverse-pattern, лишається в rego) ────────────
|
|
70
60
|
|
|
71
61
|
deny contains msg if {
|
|
72
62
|
dev := object.get(input, "devDependencies", {})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "files": ["types"] }
|
|
@@ -1,20 +1,12 @@
|
|
|
1
|
-
#
|
|
2
|
-
# (npm-module.mdc).
|
|
1
|
+
# Перевірка `.github/workflows/npm-publish.yml` (npm-module.mdc).
|
|
3
2
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# Перевіряє: `on.push.paths` містить glob з `npm/**`, `on.push.branches` містить
|
|
9
|
-
# `main`, у jobs є `permissions.id-token: write` (OIDC), є крок з
|
|
10
|
-
# `uses: JS-DevTools/npm-publish` і `with.package: npm/package.json`.
|
|
3
|
+
# Канон надходить через --data: { "template": { "snippet": ... } }
|
|
4
|
+
# Структура --data сформована з template/npm-publish.yml.snippet.yml.
|
|
5
|
+
# Per-concern field-by-field: path/substring-маркери з expected_uses_set читаються
|
|
6
|
+
# зі steps template, експектації branches/paths — subset-of.
|
|
11
7
|
#
|
|
12
8
|
# Універсальні workflow-перевірки (concurrency, заборонені setup-bun/cache/install,
|
|
13
9
|
# shell line-continuation) — у `ga.workflow_common`.
|
|
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
10
|
package npm_module.npm_publish_yml
|
|
19
11
|
|
|
20
12
|
import rego.v1
|
|
@@ -22,58 +14,74 @@ import rego.v1
|
|
|
22
14
|
# YAML 1.1 quirk: ключ `on:` → boolean true → у конфтесті ключ "true".
|
|
23
15
|
gha_on := input["true"]
|
|
24
16
|
|
|
25
|
-
#
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
])
|
|
17
|
+
# Required marker — substring у `uses` для ідентифікації npm-publish кроку.
|
|
18
|
+
publish_action_marker := "JS-DevTools/npm-publish"
|
|
19
|
+
|
|
20
|
+
# Очікувані літерали з template.
|
|
21
|
+
expected_paths := {p | some p in data.template.snippet.on.push.paths}
|
|
31
22
|
|
|
32
|
-
|
|
23
|
+
expected_branches := {b | some b in data.template.snippet.on.push.branches}
|
|
24
|
+
|
|
25
|
+
expected_permissions := data.template.snippet.jobs.publish.permissions
|
|
26
|
+
|
|
27
|
+
# Required publish-step (за маркером): expected `with.package` value з template.
|
|
28
|
+
expected_publish_with_package := s.with.package if {
|
|
29
|
+
some s in data.template.snippet.jobs.publish.steps
|
|
30
|
+
contains(object.get(s, "uses", ""), publish_action_marker)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# ── deny: paths містить кожне з expected_paths (subset-of) ───────────────
|
|
33
34
|
|
|
34
35
|
deny contains msg if {
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
some required_path in expected_paths
|
|
37
|
+
not path_present(required_path)
|
|
38
|
+
msg := sprintf("npm-publish.yml: у on.push.paths має бути `%s` (npm-module.mdc)", [required_path])
|
|
37
39
|
}
|
|
38
40
|
|
|
41
|
+
# ── deny: branches містить кожне з expected_branches (subset-of) ─────────
|
|
42
|
+
|
|
39
43
|
deny contains msg if {
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
some required_branch in expected_branches
|
|
45
|
+
not required_branch in {b | some b in gha_on.push.branches}
|
|
46
|
+
msg := sprintf("npm-publish.yml: on.push.branches має містити `%s` (npm-module.mdc)", [required_branch])
|
|
42
47
|
}
|
|
43
48
|
|
|
44
49
|
# ── deny: id-token: write у permissions хоч одного job ────────────────────
|
|
45
50
|
|
|
46
51
|
deny contains msg if {
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
required := expected_permissions["id-token"]
|
|
53
|
+
not any_job_has_id_token(required)
|
|
54
|
+
msg := sprintf("npm-publish.yml: permissions має містити `id-token: %s` (OIDC) (npm-module.mdc)", [required])
|
|
49
55
|
}
|
|
50
56
|
|
|
51
|
-
# ── deny: крок з uses
|
|
57
|
+
# ── deny: крок з uses-маркером npm-publish та канонічним with.package ────
|
|
52
58
|
|
|
53
|
-
deny contains
|
|
59
|
+
deny contains msg if {
|
|
54
60
|
not has_npm_publish_step
|
|
61
|
+
msg := sprintf(
|
|
62
|
+
"npm-publish.yml: очікується `uses: %s` з `with.package: %s` (npm-module.mdc)",
|
|
63
|
+
[publish_action_marker, expected_publish_with_package],
|
|
64
|
+
)
|
|
55
65
|
}
|
|
56
66
|
|
|
57
67
|
# ── helpers ────────────────────────────────────────────────────────────────
|
|
58
68
|
|
|
59
|
-
|
|
69
|
+
# Path присутній, якщо хоч один шлях у actual містить required як substring
|
|
70
|
+
# (npm/** glob у workflow може бути записаний як `npm/**` або `'npm/**'`).
|
|
71
|
+
path_present(required) if {
|
|
60
72
|
some p in gha_on.push.paths
|
|
61
73
|
is_string(p)
|
|
62
|
-
contains(p,
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
push_branches_have_main if {
|
|
66
|
-
"main" in {b | some b in gha_on.push.branches}
|
|
74
|
+
contains(p, required)
|
|
67
75
|
}
|
|
68
76
|
|
|
69
|
-
|
|
77
|
+
any_job_has_id_token(required) if {
|
|
70
78
|
some job in object.get(input, "jobs", {})
|
|
71
|
-
job.permissions["id-token"] ==
|
|
79
|
+
job.permissions["id-token"] == required
|
|
72
80
|
}
|
|
73
81
|
|
|
74
82
|
has_npm_publish_step if {
|
|
75
83
|
some job in object.get(input, "jobs", {})
|
|
76
84
|
some step in object.get(job, "steps", [])
|
|
77
|
-
contains(object.get(step, "uses", ""),
|
|
78
|
-
step.with.package ==
|
|
85
|
+
contains(object.get(step, "uses", ""), publish_action_marker)
|
|
86
|
+
step.with.package == expected_publish_with_package
|
|
79
87
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: npm-publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
paths:
|
|
6
|
+
- 'npm/**'
|
|
7
|
+
branches:
|
|
8
|
+
- main
|
|
9
|
+
|
|
10
|
+
concurrency:
|
|
11
|
+
group: ${{ github.ref }}-${{ github.workflow }}
|
|
12
|
+
cancel-in-progress: true
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
publish:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
permissions:
|
|
18
|
+
contents: read
|
|
19
|
+
id-token: write # КРИТИЧНО для OIDC!
|
|
20
|
+
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v6
|
|
23
|
+
with:
|
|
24
|
+
persist-credentials: false
|
|
25
|
+
|
|
26
|
+
- uses: actions/setup-node@v6
|
|
27
|
+
with:
|
|
28
|
+
node-version: '24' # includes npm@11.6.0
|
|
29
|
+
registry-url: 'https://registry.npmjs.org'
|
|
30
|
+
|
|
31
|
+
- name: Publish package
|
|
32
|
+
uses: JS-DevTools/npm-publish@v4.1.5
|
|
33
|
+
with:
|
|
34
|
+
package: npm/package.json
|