@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.
Files changed (71) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/package.json +1 -1
  3. package/rules/abie/abie.mdc +1 -4
  4. package/rules/abie/policy/clean_merged_ignore_branches/clean_merged_ignore_branches.rego +21 -40
  5. package/rules/abie/policy/clean_merged_ignore_branches/template/clean-merged-branch.yml.snippet.yml +9 -0
  6. package/rules/docker/docker.mdc +2 -50
  7. package/rules/docker/policy/lint_docker_yml/lint_docker_yml.rego +33 -47
  8. package/rules/docker/policy/lint_docker_yml/template/lint-docker.yml.snippet.yml +41 -0
  9. package/rules/docker/policy/package_json/package_json.rego +11 -28
  10. package/rules/docker/policy/package_json/template/package.json.snippet.json +1 -0
  11. package/rules/image-avif/policy/package_json/package_json.rego +21 -35
  12. package/rules/image-avif/policy/package_json/template/package.json.deny.json +5 -0
  13. package/rules/image-compress/image-compress.mdc +2 -8
  14. package/rules/image-compress/policy/package_json/package_json.rego +24 -65
  15. package/rules/image-compress/policy/package_json/template/package.json.contains.json +5 -0
  16. package/rules/image-compress/policy/package_json/template/package.json.deny.json +8 -0
  17. package/rules/js-bun-db/policy/package_json/package_json.rego +8 -21
  18. package/rules/js-bun-db/policy/package_json/template/package.json.deny.json +7 -0
  19. package/rules/js-bun-redis/policy/package_json/package_json.rego +8 -30
  20. package/rules/js-bun-redis/policy/package_json/template/package.json.deny.json +12 -0
  21. package/rules/js-lint/policy/jscpd/jscpd.rego +29 -23
  22. package/rules/js-lint/policy/jscpd/template/.jscpd.json.snippet.json +6 -0
  23. package/rules/js-lint/policy/lint_js_yml/lint_js_yml.rego +39 -47
  24. package/rules/js-lint/policy/lint_js_yml/template/lint-js.yml.snippet.yml +44 -0
  25. package/rules/js-lint/policy/package_json/package_json.rego +22 -42
  26. package/rules/js-lint/policy/package_json/template/package.json.snippet.json +6 -0
  27. package/rules/js-lint/policy/vscode_extensions/template/extensions.json.snippet.json +7 -0
  28. package/rules/js-lint/policy/vscode_extensions/vscode_extensions.rego +4 -17
  29. package/rules/js-run/policy/configmap/configmap.rego +11 -35
  30. package/rules/js-run/policy/configmap/template/configmap.yaml.contains.yml +4 -0
  31. package/rules/js-run/policy/jsconfig/jsconfig.rego +39 -46
  32. package/rules/js-run/policy/jsconfig/template/jsconfig.json.snippet.json +10 -0
  33. package/rules/js-run/policy/package_json/package_json.rego +10 -21
  34. package/rules/js-run/policy/package_json/template/package.json.deny.json +10 -0
  35. package/rules/npm-module/npm-module.mdc +7 -36
  36. package/rules/npm-module/policy/emit_types_config/emit_types_config.rego +18 -27
  37. package/rules/npm-module/policy/emit_types_config/template/tsconfig.emit-types.json.snippet.json +9 -0
  38. package/rules/npm-module/policy/npm_package_json/npm_package_json.rego +20 -30
  39. package/rules/npm-module/policy/npm_package_json/template/package.json.snippet.json +1 -0
  40. package/rules/npm-module/policy/npm_publish_yml/npm_publish_yml.rego +46 -38
  41. package/rules/npm-module/policy/npm_publish_yml/template/npm-publish.yml.snippet.yml +34 -0
  42. package/rules/npm-module/policy/root_package_json/root_package_json.rego +17 -17
  43. package/rules/npm-module/policy/root_package_json/template/package.json.snippet.json +1 -0
  44. package/rules/php/php.mdc +2 -56
  45. package/rules/php/policy/lint_php_yml/lint_php_yml.rego +15 -13
  46. package/rules/php/policy/lint_php_yml/template/lint-php.yml.snippet.yml +47 -0
  47. package/rules/php/policy/package_json/package_json.rego +9 -12
  48. package/rules/php/policy/package_json/template/package.json.contains.json +5 -0
  49. package/rules/style-lint/policy/lint_style_yml/lint_style_yml.rego +14 -16
  50. package/rules/style-lint/policy/lint_style_yml/template/lint-style.yml.snippet.yml +39 -0
  51. package/rules/style-lint/policy/package_json/package_json.rego +22 -30
  52. package/rules/style-lint/policy/package_json/template/package.json.contains.json +5 -0
  53. package/rules/style-lint/policy/package_json/template/package.json.snippet.json +5 -0
  54. package/rules/style-lint/policy/vscode_extensions/template/extensions.json.snippet.json +1 -0
  55. package/rules/style-lint/policy/vscode_extensions/vscode_extensions.rego +5 -15
  56. package/rules/style-lint/policy/vscode_settings/template/settings.json.snippet.json +5 -0
  57. package/rules/style-lint/policy/vscode_settings/vscode_settings.rego +7 -16
  58. package/rules/text/policy/cspell/cspell.rego +39 -59
  59. package/rules/text/policy/cspell/template/.cspell.json.contains.json +3 -0
  60. package/rules/text/policy/cspell/template/.cspell.json.deny.json +5 -0
  61. package/rules/text/policy/cspell/template/.cspell.json.snippet.json +12 -0
  62. package/rules/text/policy/markdownlint/markdownlint.rego +37 -50
  63. package/rules/text/policy/markdownlint/template/.markdownlint-cli2.jsonc.snippet.jsonc +11 -0
  64. package/rules/text/policy/oxfmtrc/oxfmtrc.rego +23 -58
  65. package/rules/text/policy/oxfmtrc/template/.oxfmtrc.json.snippet.json +12 -0
  66. package/rules/text/policy/package_json/package_json.rego +14 -52
  67. package/rules/text/policy/package_json/template/package.json.deny.json +15 -0
  68. package/rules/text/policy/vscode_extensions/template/extensions.json.snippet.json +7 -0
  69. package/rules/text/policy/vscode_extensions/vscode_extensions.rego +4 -28
  70. package/rules/text/policy/vscode_settings/template/settings.json.snippet.json +9 -0
  71. 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
- # Canonical: у `recommendations` мають бути ESLint, GitHub Actions і Oxlint.
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 required in required_extensions
23
- not required in recommendations_set
24
- msg := sprintf(".vscode/extensions.json: recommendations має містити %q (js-lint.mdc)", [required])
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
- # Порт перевірки `k8s/base/configmap.yaml` з `npm/scripts/check-js-run.mjs`
2
- # (js-run.mdc) — `OTEL_RESOURCE_ATTRIBUTES` має містити `service.name=` і
3
- # `service.namespace=`.
1
+ # Перевірка ConfigMap (js-run.mdc).
4
2
  #
5
- # Запуск (локально):
6
- # conftest test path/to/k8s/base/configmap.yaml -p npm/policy/js_run \
7
- # --namespace js_run.configmap
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
- otel := object.get(object.get(input, "data", {}), "OTEL_RESOURCE_ATTRIBUTES", "")
40
- otel != ""
41
- not contains(otel, "service.namespace=")
42
- msg := sprintf(otel_service_namespace_template, [cm_name])
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", "?")
@@ -0,0 +1,4 @@
1
+ data:
2
+ OTEL_RESOURCE_ATTRIBUTES:
3
+ - 'service.name='
4
+ - 'service.namespace='
@@ -1,66 +1,59 @@
1
- # Порт перевірки `jsconfig.json` з `npm/scripts/check-js-run.mjs` (js-run.mdc).
1
+ # Перевірка `jsconfig.json` (js-run.mdc).
2
2
  #
3
- # Запуск (локально, у backend-пакеті з каталогом `src/`):
4
- # conftest test path/to/jsconfig.json -p npm/policy/js_run \
5
- # --namespace js_run.jsconfig
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
- # ── deny: compilerOptions ──────────────────────────────────────────────────
21
-
11
+ # Generic 2-level walker: section → key → expected (для compilerOptions).
22
12
  deny contains msg if {
23
- co := object.get(input, "compilerOptions", {})
24
- not is_array(object.get(co, "lib", null))
25
- msg := "jsconfig.json: compilerOptions.lib має бути [\"esnext\"] (js-run.mdc)"
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
- co := object.get(input, "compilerOptions", {})
30
- is_array(co.lib)
31
- {l | some l in co.lib} != {"esnext"}
32
- msg := "jsconfig.json: compilerOptions.lib має бути [\"esnext\"] (js-run.mdc)"
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
- object.get(object.get(input, "compilerOptions", {}), "module", null) != "NodeNext"
37
- msg := "jsconfig.json: compilerOptions.module має бути \"NodeNext\" (js-run.mdc)"
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
- object.get(object.get(input, "compilerOptions", {}), "moduleResolution", null) != "NodeNext"
42
- msg := "jsconfig.json: compilerOptions.moduleResolution має бути \"NodeNext\" (js-run.mdc)"
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
- deny contains msg if {
46
- object.get(object.get(input, "compilerOptions", {}), "target", null) != "esnext"
47
- msg := "jsconfig.json: compilerOptions.target має бути \"esnext\" (js-run.mdc)"
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
- deny contains msg if {
51
- object.get(object.get(input, "compilerOptions", {}), "checkJs", null) != false
52
- msg := "jsconfig.json: compilerOptions.checkJs має бути false (js-run.mdc)"
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
  }
@@ -0,0 +1,10 @@
1
+ {
2
+ "compilerOptions": {
3
+ "lib": ["esnext"],
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "target": "esnext",
7
+ "checkJs": false
8
+ },
9
+ "include": ["src/**/*"]
10
+ }
@@ -1,31 +1,20 @@
1
- # Порт перевірки залежностей `package.json` з `npm/scripts/check-js-run.mjs`
2
- # (js-run.mdc) — заборона `bunyan` / `@nitra/bunyan`.
1
+ # Перевірка `package.json` (js-run.mdc).
3
2
  #
4
- # Запуск (локально, для будь-якого `package.json` у дереві):
5
- # conftest test path/to/package.json -p npm/policy/js_run \
6
- # --namespace js_run.package_json
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 pkg_name in forbidden_packages
23
- pkg_name in object.keys(object.get(input, "dependencies", {}))
24
- msg := sprintf("dependencies містить %q — використовуй стандартні логери (js-run.mdc)", [pkg_name])
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 pkg_name in forbidden_packages
29
- pkg_name in object.keys(object.get(input, "devDependencies", {}))
30
- msg := sprintf("devDependencies містить %q — використовуй стандартні логери (js-run.mdc)", [pkg_name])
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
- ```yaml title=".github/workflows/npm-publish.yml"
79
- name: npm-publish
80
-
81
- on:
82
- push:
83
- paths:
84
- - 'npm/**'
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
- # Порт перевірок `npm/tsconfig.emit-types.json` з `npm/scripts/check-npm-module.mjs`
2
- # (npm-module.mdc).
1
+ # Перевірка `npm/tsconfig.emit-types.json` (npm-module.mdc).
3
2
  #
4
- # Запуск (локально):
5
- # conftest test npm/tsconfig.emit-types.json -p npm/policy/npm_module \
6
- # --namespace npm_module.emit_types_config
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
- required_compiler_options := {
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
- not is_object(object.get(input, "compilerOptions", null))
29
- msg := "npm/tsconfig.emit-types.json: відсутній compilerOptions (npm-module.mdc)"
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
- is_object(input.compilerOptions)
34
- some key, expected in required_compiler_options
35
- object.get(input.compilerOptions, key, null) != expected
36
- msg := sprintf("npm/tsconfig.emit-types.json: compilerOptions.%s має бути %v (npm-module.mdc)", [key, expected])
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
  }
@@ -0,0 +1,9 @@
1
+ {
2
+ "compilerOptions": {
3
+ "allowJs": true,
4
+ "declaration": true,
5
+ "emitDeclarationOnly": true,
6
+ "outDir": "types",
7
+ "skipLibCheck": true
8
+ }
9
+ }
@@ -1,44 +1,29 @@
1
- # Порт перевірок `npm/package.json` з `npm/scripts/check-npm-module.mjs`
2
- # (npm-module.mdc).
1
+ # Порт перевірок `npm/package.json` (npm-module.mdc).
3
2
  #
4
- # Запуск (локально):
5
- # conftest test npm/package.json -p npm/policy/npm_module \
6
- # --namespace npm_module.npm_package_json
3
+ # Канон надходить через --data: { "template": { "snippet": ... } }
4
+ # Структура --data сформована з template/package.json.snippet.json
5
+ # (snippet-array subset-of для whitelist `files`).
7
6
  #
8
- # Перевіряє:
9
- # - поле `types` має один із двох канонічних патернів: `./types/index.d.ts`
10
- # (layout `npm/src` з `.js`) або `./types/<…>.d.ts`/`.d.mts` (emit-types);
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
- # Те, який саме типовий layout активний (наявність `.js` під `npm/src`),
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 має існувати, бути непорожнім, містити "types" ────────────
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
- is_array(input.files)
64
- count(input.files) > 0
65
- not "types" in {f | some f in input.files}
66
- msg := "npm/package.json: масив \"files\" має містити \"types\" (npm-module.mdc)"
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: жодних devDependencies у npm/package.json ───────────────────────
59
+ # ── deny: devDependencies (inverse-pattern, лишається в rego) ────────────
70
60
 
71
61
  deny contains msg if {
72
62
  dev := object.get(input, "devDependencies", {})
@@ -1,20 +1,12 @@
1
- # Порт перевірок `.github/workflows/npm-publish.yml` з `npm/scripts/check-npm-module.mjs`
2
- # (npm-module.mdc).
1
+ # Перевірка `.github/workflows/npm-publish.yml` (npm-module.mdc).
3
2
  #
4
- # Запуск (локально):
5
- # conftest test .github/workflows/npm-publish.yml -p npm/policy/npm_module \
6
- # --namespace npm_module.npm_publish_yml
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
- # Шаблон повідомлення про відсутній JS-DevTools/npm-publish крок — через `concat`
26
- # для regal style/line-length.
27
- npm_publish_step_template := concat(" ", [
28
- "npm-publish.yml: очікується `uses: JS-DevTools/npm-publish`",
29
- `with.package: npm/package.json` (npm-module.mdc)",
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
- # ── deny: paths/branches ──────────────────────────────────────────────────
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
- not push_paths_have_npm_glob
36
- msg := "npm-publish.yml: у on.push.paths має бути `npm/**` (npm-module.mdc)"
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
- not push_branches_have_main
41
- msg := "npm-publish.yml: on.push.branches має містити `main` (npm-module.mdc)"
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
- not any_job_has_id_token_write
48
- msg := "npm-publish.yml: permissions має містити `id-token: write` (OIDC) (npm-module.mdc)"
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 JS-DevTools/npm-publish та with.package ─────────────
57
+ # ── deny: крок з uses-маркером npm-publish та канонічним with.package ────
52
58
 
53
- deny contains npm_publish_step_template if {
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
- push_paths_have_npm_glob if {
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, "npm/**")
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
- any_job_has_id_token_write if {
77
+ any_job_has_id_token(required) if {
70
78
  some job in object.get(input, "jobs", {})
71
- job.permissions["id-token"] == "write"
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", ""), "JS-DevTools/npm-publish")
78
- step.with.package == "npm/package.json"
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