@7n/rules-lang-js 0.3.1 → 0.4.1
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 +12 -0
- package/package.json +14 -4
- package/rules/bun/bunfig/bunfig.mdc +17 -0
- package/rules/bun/bunfig/bunfig.rego +29 -0
- package/rules/bun/bunfig/concern.json +9 -0
- package/rules/bun/bunfig/template/bunfig.toml.snippet.toml +2 -0
- package/rules/bun/docs/index.md +11 -0
- package/rules/bun/layout/concern.json +16 -0
- package/rules/bun/layout/docs/fix-layout.md +29 -0
- package/rules/bun/layout/docs/main.md +34 -0
- package/rules/bun/layout/fix-layout.mjs +63 -0
- package/rules/bun/layout/layout.mdc +60 -0
- package/rules/bun/layout/main.mjs +53 -0
- package/rules/bun/licensee/concern.json +7 -0
- package/rules/bun/licensee/docs/fix-licensee.md +27 -0
- package/rules/bun/licensee/docs/index.md +12 -0
- package/rules/bun/licensee/docs/main.md +30 -0
- package/rules/bun/licensee/fix-licensee.mjs +31 -0
- package/rules/bun/licensee/main.mjs +68 -0
- package/rules/bun/lint-surface/concern.json +3 -0
- package/rules/bun/lint-surface/lint-surface.mdc +13 -0
- package/rules/bun/main.json +1 -0
- package/rules/bun/main.mdc +11 -0
- package/rules/bun/package_json/concern.json +9 -0
- package/rules/bun/package_json/docs/fix-package_json.md +28 -0
- package/rules/bun/package_json/docs/index.md +9 -0
- package/rules/bun/package_json/fix-package_json.mjs +311 -0
- package/rules/bun/package_json/package_json.mdc +14 -0
- package/rules/bun/package_json/package_json.rego +64 -0
- package/rules/bun/package_json/template/package.json.deny.json +4 -0
- package/rules/js/check/check.mdc +26 -0
- package/rules/js/check/concern.json +19 -0
- package/rules/js/check/docs/eslint-config.md +56 -0
- package/rules/js/check/docs/fix-check.md +48 -0
- package/rules/js/check/docs/index.md +13 -0
- package/rules/js/check/docs/main.md +48 -0
- package/rules/js/check/eslint-config.mjs +262 -0
- package/rules/js/check/fix-check.mjs +82 -0
- package/rules/js/check/main.mjs +310 -0
- package/rules/js/dep-policy/concern.json +4 -0
- package/rules/js/dep-policy/dep-policy.mdc +36 -0
- package/rules/js/dep-policy/docs/main.md +35 -0
- package/rules/js/dep-policy/main.mjs +99 -0
- package/rules/js/docs/index.md +11 -0
- package/rules/js/eslint/concern.json +8 -0
- package/rules/js/eslint/docs/fix-eslint.md +50 -0
- package/rules/js/eslint/docs/fix-worker.md +31 -0
- package/rules/js/eslint/docs/index.md +11 -0
- package/rules/js/eslint/docs/main.md +35 -0
- package/rules/js/eslint/fix-eslint.mjs +160 -0
- package/rules/js/eslint/fix-worker.mjs +139 -0
- package/rules/js/eslint/main.mjs +115 -0
- package/rules/js/file-extensions/concern.json +3 -0
- package/rules/js/file-extensions/file-extensions.mdc +12 -0
- package/rules/js/jscpd_config/concern.json +11 -0
- package/rules/js/jscpd_config/docs/fix-jscpd_config.md +25 -0
- package/rules/js/jscpd_config/docs/index.md +9 -0
- package/rules/js/jscpd_config/fix-jscpd_config.mjs +3 -0
- package/rules/js/jscpd_config/jscpd_config.mdc +42 -0
- package/rules/js/jscpd_config/jscpd_config.rego +44 -0
- package/rules/js/jscpd_config/template/.jscpd.json.snippet.json +7 -0
- package/rules/js/jscpd_duplicates/concern.json +7 -0
- package/rules/js/jscpd_duplicates/docs/main.md +29 -0
- package/rules/js/jscpd_duplicates/main.mjs +69 -0
- package/rules/js/knip/concern.json +7 -0
- package/rules/js/knip/docs/main.md +31 -0
- package/rules/js/knip/knip.mdc +15 -0
- package/rules/js/knip/main.mjs +89 -0
- package/rules/js/lint-findings/concern.json +3 -0
- package/rules/js/lint-findings/docs/main.md +40 -0
- package/rules/js/lint-findings/main.mjs +125 -0
- package/rules/js/main.json +1 -0
- package/rules/js/main.mdc +18 -0
- package/rules/js/package_json/concern.json +9 -0
- package/rules/js/package_json/docs/fix-package_json.md +25 -0
- package/rules/js/package_json/docs/index.md +9 -0
- package/rules/js/package_json/fix-package_json.mjs +3 -0
- package/rules/js/package_json/package_json.mdc +15 -0
- package/rules/js/package_json/package_json.rego +142 -0
- package/rules/js/package_json/template/package.json.snippet.json +6 -0
- package/rules/js/tooling/concern.json +3 -0
- package/rules/js/tooling/data/tooling/knip-canonical.json +30 -0
- package/rules/js/tooling/data/tooling/oxlint-canonical.json +400 -0
- package/rules/js/tooling/docs/main.md +53 -0
- package/rules/js/tooling/main.mjs +183 -0
- package/rules/js/utils_imports/concern.json +4 -0
- package/rules/js/utils_imports/docs/main.md +50 -0
- package/rules/js/utils_imports/main.mjs +185 -0
- package/rules/js/utils_imports/utils_imports.mdc +15 -0
- package/rules/js/vscode_extensions/concern.json +11 -0
- package/rules/js/vscode_extensions/docs/fix-vscode_extensions.md +24 -0
- package/rules/js/vscode_extensions/docs/index.md +11 -0
- package/rules/js/vscode_extensions/fix-vscode_extensions.mjs +1 -0
- package/rules/js/vscode_extensions/template/extensions.json.snippet.json +6 -0
- package/rules/js/vscode_extensions/vscode_extensions.mdc +11 -0
- package/rules/js/vscode_extensions/vscode_extensions.rego +12 -0
- package/rules/js-bun-db/connection/concern.json +3 -0
- package/rules/js-bun-db/connection/connection.mdc +42 -0
- package/rules/js-bun-db/docs/index.md +11 -0
- package/rules/js-bun-db/lib/bun-sql-scan.mjs +1047 -0
- package/rules/js-bun-db/lib/docs/bun-sql-scan.md +63 -0
- package/rules/js-bun-db/lib/docs/index.md +11 -0
- package/rules/js-bun-db/main.json +1 -0
- package/rules/js-bun-db/main.mdc +8 -0
- package/rules/js-bun-db/package_json/concern.json +9 -0
- package/rules/js-bun-db/package_json/package_json.mdc +31 -0
- package/rules/js-bun-db/package_json/package_json.rego +15 -0
- package/rules/js-bun-db/package_json/template/package.json.deny.json +6 -0
- package/rules/js-bun-db/pg_format_identifiers/concern.json +3 -0
- package/rules/js-bun-db/pg_format_identifiers/pg_format_identifiers.mdc +104 -0
- package/rules/js-bun-db/safety/concern.json +4 -0
- package/rules/js-bun-db/safety/docs/main.md +34 -0
- package/rules/js-bun-db/safety/main.mjs +430 -0
- package/rules/js-bun-db/safety/safety.mdc +458 -0
- package/rules/js-bun-redis/docs/index.md +11 -0
- package/rules/js-bun-redis/imports/concern.json +4 -0
- package/rules/js-bun-redis/imports/docs/main.md +36 -0
- package/rules/js-bun-redis/imports/imports.mdc +47 -0
- package/rules/js-bun-redis/imports/main.mjs +88 -0
- package/rules/js-bun-redis/lib/docs/index.md +11 -0
- package/rules/js-bun-redis/lib/docs/redis-imports.md +227 -0
- package/rules/js-bun-redis/lib/redis-imports.mjs +130 -0
- package/rules/js-bun-redis/main.json +1 -0
- package/rules/js-bun-redis/main.mdc +8 -0
- package/rules/js-bun-redis/package_json/concern.json +9 -0
- package/rules/js-bun-redis/package_json/package_json.mdc +11 -0
- package/rules/js-bun-redis/package_json/package_json.rego +15 -0
- package/rules/js-bun-redis/package_json/template/package.json.deny.json +12 -0
- package/rules/js-mssql/deps/concern.json +4 -0
- package/rules/js-mssql/deps/docs/main.md +33 -0
- package/rules/js-mssql/deps/main.mjs +297 -0
- package/rules/js-mssql/docs/index.md +11 -0
- package/rules/js-mssql/lib/docs/index.md +11 -0
- package/rules/js-mssql/lib/docs/mssql-pool-scan.md +380 -0
- package/rules/js-mssql/lib/mssql-pool-scan.mjs +610 -0
- package/rules/js-mssql/main.json +1 -0
- package/rules/js-mssql/main.mdc +144 -0
- package/rules/js-mssql/mssql-tvp/concern.json +3 -0
- package/rules/js-mssql/mssql-tvp/mssql-tvp.mdc +77 -0
- package/rules/js-mssql/package_json/concern.json +9 -0
- package/rules/js-mssql/package_json/package_json.mdc +9 -0
- package/rules/js-mssql/package_json/package_json.rego +57 -0
- package/rules/js-run/configmap/concern.json +9 -0
- package/rules/js-run/configmap/configmap.mdc +37 -0
- package/rules/js-run/configmap/configmap.rego +21 -0
- package/rules/js-run/configmap/template/configmap.yaml.contains.yml +4 -0
- package/rules/js-run/docs/index.md +11 -0
- package/rules/js-run/jsconfig/concern.json +9 -0
- package/rules/js-run/jsconfig/docs/fix-jsconfig.md +28 -0
- package/rules/js-run/jsconfig/docs/index.md +9 -0
- package/rules/js-run/jsconfig/fix-jsconfig.mjs +119 -0
- package/rules/js-run/jsconfig/jsconfig.mdc +48 -0
- package/rules/js-run/jsconfig/jsconfig.rego +59 -0
- package/rules/js-run/jsconfig/template/jsconfig.json.snippet.json +10 -0
- package/rules/js-run/lib/bunyan-imports.mjs +98 -0
- package/rules/js-run/lib/check-env-scan.mjs +338 -0
- package/rules/js-run/lib/conn-file-rules.mjs +214 -0
- package/rules/js-run/lib/conn-imports-scan.mjs +154 -0
- package/rules/js-run/lib/docs/bunyan-imports.md +121 -0
- package/rules/js-run/lib/docs/check-env-scan.md +438 -0
- package/rules/js-run/lib/docs/conn-file-rules.md +304 -0
- package/rules/js-run/lib/docs/conn-imports-scan.md +208 -0
- package/rules/js-run/lib/docs/index.md +16 -0
- package/rules/js-run/lib/docs/promise-settimeout-scan.md +334 -0
- package/rules/js-run/lib/docs/temporal-scan.md +29 -0
- package/rules/js-run/lib/promise-settimeout-scan.mjs +128 -0
- package/rules/js-run/lib/temporal-scan.mjs +52 -0
- package/rules/js-run/main.json +1 -0
- package/rules/js-run/main.mdc +16 -0
- package/rules/js-run/package_json/concern.json +9 -0
- package/rules/js-run/package_json/package_json.mdc +44 -0
- package/rules/js-run/package_json/package_json.rego +37 -0
- package/rules/js-run/package_json/template/package.json.deny.json +22 -0
- package/rules/js-run/project-structure/concern.json +3 -0
- package/rules/js-run/project-structure/project-structure.mdc +11 -0
- package/rules/js-run/runtime/concern.json +12 -0
- package/rules/js-run/runtime/docs/fix-runtime.md +27 -0
- package/rules/js-run/runtime/docs/main.md +35 -0
- package/rules/js-run/runtime/fix-runtime.mjs +46 -0
- package/rules/js-run/runtime/main.mjs +496 -0
- package/rules/js-run/runtime/runtime.mdc +184 -0
- package/rules/js-run/scope/concern.json +3 -0
- package/rules/js-run/scope/scope.mdc +11 -0
- package/rules/npm-module/docs/index.md +11 -0
- package/rules/npm-module/emit_types_config/concern.json +9 -0
- package/rules/npm-module/emit_types_config/docs/fix-emit_types_config.md +25 -0
- package/rules/npm-module/emit_types_config/docs/index.md +9 -0
- package/rules/npm-module/emit_types_config/emit_types_config.mdc +43 -0
- package/rules/npm-module/emit_types_config/emit_types_config.rego +28 -0
- package/rules/npm-module/emit_types_config/fix-emit_types_config.mjs +5 -0
- package/rules/npm-module/emit_types_config/template/tsconfig.emit-types.json.snippet.json +9 -0
- package/rules/npm-module/header_doc_pointer/concern.json +5 -0
- package/rules/npm-module/header_doc_pointer/docs/main.md +40 -0
- package/rules/npm-module/header_doc_pointer/header_doc_pointer.mdc +18 -0
- package/rules/npm-module/header_doc_pointer/main.mjs +131 -0
- package/rules/npm-module/main.json +1 -0
- package/rules/npm-module/main.mdc +36 -0
- package/rules/npm-module/npm_package_json/concern.json +9 -0
- package/rules/npm-module/npm_package_json/docs/fix-npm_package_json.md +26 -0
- package/rules/npm-module/npm_package_json/docs/index.md +9 -0
- package/rules/npm-module/npm_package_json/fix-npm_package_json.mjs +5 -0
- package/rules/npm-module/npm_package_json/npm_package_json.mdc +57 -0
- package/rules/npm-module/npm_package_json/npm_package_json.rego +73 -0
- package/rules/npm-module/npm_package_json/template/package.json.snippet.json +1 -0
- package/rules/npm-module/package_structure/concern.json +5 -0
- package/rules/npm-module/package_structure/docs/main.md +37 -0
- package/rules/npm-module/package_structure/main.mjs +448 -0
- package/rules/npm-module/package_structure/package_structure.mdc +63 -0
- package/rules/npm-module/root_package_json/concern.json +9 -0
- package/rules/npm-module/root_package_json/docs/fix-root_package_json.md +25 -0
- package/rules/npm-module/root_package_json/docs/index.md +9 -0
- package/rules/npm-module/root_package_json/fix-root_package_json.mjs +5 -0
- package/rules/npm-module/root_package_json/root_package_json.mdc +41 -0
- package/rules/npm-module/root_package_json/root_package_json.rego +28 -0
- package/rules/npm-module/root_package_json/template/package.json.snippet.json +1 -0
- package/rules/npm-module/rule_meta/concern.json +8 -0
- package/rules/npm-module/rule_meta/docs/main.md +35 -0
- package/rules/npm-module/rule_meta/main.mjs +119 -0
- package/rules/npm-module/rule_meta/rule_meta.mdc +11 -0
- package/rules/npm-module/skill_meta/concern.json +5 -0
- package/rules/npm-module/skill_meta/docs/main.md +149 -0
- package/rules/npm-module/skill_meta/main.mjs +91 -0
- package/rules/npm-module/skill_meta/skill_meta.mdc +11 -0
- package/rules/tool-surface/docs/index.md +11 -0
- package/rules/tool-surface/main.json +6 -0
- package/rules/tool-surface/main.mdc +72 -0
- package/rules/vue/composition-api/composition-api.mdc +82 -0
- package/rules/vue/composition-api/concern.json +3 -0
- package/rules/vue/docs/index.md +11 -0
- package/rules/vue/lib/docs/index.md +11 -0
- package/rules/vue/lib/docs/vue-forbidden-imports.md +265 -0
- package/rules/vue/lib/vue-forbidden-imports.mjs +240 -0
- package/rules/vue/main.json +1 -0
- package/rules/vue/main.mdc +18 -0
- package/rules/vue/nheader-layout/concern.json +3 -0
- package/rules/vue/nheader-layout/nheader-layout.mdc +171 -0
- package/rules/vue/package_json/concern.json +9 -0
- package/rules/vue/package_json/package_json.mdc +30 -0
- package/rules/vue/package_json/package_json.rego +140 -0
- package/rules/vue/packages/concern.json +6 -0
- package/rules/vue/packages/docs/index.md +11 -0
- package/rules/vue/packages/docs/main.md +35 -0
- package/rules/vue/packages/main.mjs +575 -0
- package/rules/vue/packages/packages.mdc +56 -0
- package/rules/vue/quasar-ui/concern.json +3 -0
- package/rules/vue/quasar-ui/quasar-ui.mdc +32 -0
- package/rules/vue/structure/concern.json +3 -0
- package/rules/vue/structure/structure.mdc +101 -0
- package/rules/vue/testing/concern.json +3 -0
- package/rules/vue/testing/testing.mdc +40 -0
- package/rules/vue/tfm-translations/concern.json +7 -0
- package/rules/vue/tfm-translations/docs/main.md +29 -0
- package/rules/vue/tfm-translations/main.mjs +55 -0
- package/rules/vue/tfm-translations/tfm-translations.mdc +32 -0
- package/rules/vue/vite-config/concern.json +3 -0
- package/rules/vue/vite-config/vite-config.mdc +153 -0
- package/rules/vue/vite-env/concern.json +3 -0
- package/rules/vue/vite-env/vite-env.mdc +61 -0
|
@@ -0,0 +1,22 @@
|
|
|
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
|
+
"scriptsForbidden": [
|
|
11
|
+
{
|
|
12
|
+
"id": "node-runner",
|
|
13
|
+
"pattern": "\\bnode(\\s|$)",
|
|
14
|
+
"message": "заміни `node` на `bun` у scripts — один runtime у dev і prod (js-run.mdc)"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"id": "env-cat-bun",
|
|
18
|
+
"pattern": "\\benv\\s+\\$\\(cat\\s+[^)]+\\)\\s+bun\\b",
|
|
19
|
+
"message": "заміни `env $(cat A B) bun` на `bun --env-file=A --env-file=B` (нативний Bun, js-run.mdc)"
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
## Структура проекту
|
|
2
|
+
|
|
3
|
+
Рекомендується використовувати таку структуру проекту:
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
k8s/ # тут всі файли для деплойменту в Kubernetes, включаючи kustomize
|
|
7
|
+
src/ # тут всі файли необхідні для роботи проекту
|
|
8
|
+
Dockerfile
|
|
9
|
+
package.json
|
|
10
|
+
readme.md
|
|
11
|
+
```
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: fix-runtime.mjs
|
|
4
|
+
resource: plugins/lang-js/rules/js-run/runtime/fix-runtime.mjs
|
|
5
|
+
docgen:
|
|
6
|
+
crc: a357e26a
|
|
7
|
+
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
8
|
+
score: 100
|
|
9
|
+
judgeModel: openai-codex/gpt-5.4-mini
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
Процес T0-autofix автоматично створює стандартизований `jsconfig.json` у backend-пакетах, що знаходяться в каталозі `src/`, якщо такий конфігураційний файл відсутній. Для визначення канонічного вигляду цього файлу використовується шаблон, визначений у `policy/jsconfig/template/jsconfig.json.snippet.json`.
|
|
13
|
+
|
|
14
|
+
Конфіги, на які спирається код: jsconfig.json, jsconfig.json.snippet.json
|
|
15
|
+
|
|
16
|
+
## Поведінка
|
|
17
|
+
|
|
18
|
+
1. Ініціалізується контент канонічного файлу `jsconfig.json` шляхом читання з `jsconfig.json.snippet.json`.
|
|
19
|
+
2. Протягується масив `patterns` для ідентифікації робочих простірців, яким бракує файлу `jsconfig.json`.
|
|
20
|
+
3. Для кожного шаблону в `patterns` перевіряється наявність підходящого вихідного стану.
|
|
21
|
+
4. Якщо шаблон спрацьовує, здійснюється пошук усіх відповідних робочих простірців у контексті.
|
|
22
|
+
5. Для кожного знайденого робочого простірця, який містить каталог `src/` та не має `jsconfig.json`, відбувається запис канонічного контенту у файл `jsconfig.json`.
|
|
23
|
+
6. У результаті, що створюються файли `jsconfig.json` у потрібних пакетах фіксується та повертається відповідний статус.
|
|
24
|
+
|
|
25
|
+
## Гарантії поведінки
|
|
26
|
+
|
|
27
|
+
- (специфічних машинно-виведених гарантій немає)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: main.mjs
|
|
4
|
+
resource: plugins/lang-js/rules/js-run/runtime/main.mjs
|
|
5
|
+
docgen:
|
|
6
|
+
crc: 9e5d4e7b
|
|
7
|
+
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
8
|
+
score: 100
|
|
9
|
+
issues: judge:inaccurate:0.99
|
|
10
|
+
judgeModel: openai-codex/gpt-5.4-mini
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Огляд
|
|
14
|
+
|
|
15
|
+
Цей файл забезпечує відповідність проєкту затвердженим правилам `js-run.mdc`, спираючись на конфігурації `jsconfig.json` та `package.json`. Він перевіряє конфігурації, імпорти, неймінг, змінні середовища та таймери для кожного пакета, ігноруючи перевірку для frontend-пакетів, що використовують `vite`.
|
|
16
|
+
|
|
17
|
+
## Поведінка
|
|
18
|
+
|
|
19
|
+
Поведінка:
|
|
20
|
+
pgWriteContract: Не є публічною функцією.
|
|
21
|
+
main: Перевіряє відповідність проєкту правилам `js-run.mdc` для кожного workspace-пакета, пропускаючи перевірку для frontend-пакетів, що використовують `vite`. Для пакетів відбувається валідація конфігурацій, імпортів, неймінгу файлів, використання змінних середовища та таймерів.
|
|
22
|
+
|
|
23
|
+
## Публічний API
|
|
24
|
+
|
|
25
|
+
_Конфігураційні файли, на які спирається код: jsconfig.json, package.json_
|
|
26
|
+
|
|
27
|
+
- pgWriteContract — Формує та впроваджує контракт до бази даних, використовуючи специфічний інструментарій.\*
|
|
28
|
+
- main — Аудитує відповідність правил виконання JavaScript для окремих пакетів у структурі workspace, ігноруючи корінь репозиторію.
|
|
29
|
+
|
|
30
|
+
## Гарантії поведінки
|
|
31
|
+
|
|
32
|
+
- Read-only: не виконує операцій запису (ФС/БД).
|
|
33
|
+
- Перехоплює помилки і не пропускає винятків назовні (fail-safe).
|
|
34
|
+
- За певних помилок повертає порожнє значення (напр. `null`) замість винятку.
|
|
35
|
+
- Свідомо пропускає шляхи: `base/`.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T0-autofix для `js-run/runtime` — детерміноване створення канонічного
|
|
3
|
+
* `jsconfig.json` у backend-пакетах із `src/` де він відсутній. Канон читається
|
|
4
|
+
* з `jsconfig/template/jsconfig.json.snippet.json` (єдине джерело істини).
|
|
5
|
+
*
|
|
6
|
+
* Unified lint surface: structured violations (test(violations)/apply(violations,ctx)).
|
|
7
|
+
* Workspace-и читаються з `v.message` ("[<ws>] є каталог src/, але немає jsconfig.json").
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
10
|
+
import { join } from 'node:path'
|
|
11
|
+
import { fileURLToPath } from 'node:url'
|
|
12
|
+
|
|
13
|
+
const JSCONFIG_MISSING_RE = /є каталог src\/, але немає jsconfig\.json/u
|
|
14
|
+
/** Витягує workspace із message-а одного violation (`[<ws>] є каталог src/…`). */
|
|
15
|
+
const JSCONFIG_MISSING_WS_RE = /^\[([^\]]*)\] є каталог src\/, але немає jsconfig\.json/u
|
|
16
|
+
|
|
17
|
+
const JSCONFIG_CONTENT =
|
|
18
|
+
readFileSync(
|
|
19
|
+
fileURLToPath(new URL('../jsconfig/template/jsconfig.json.snippet.json', import.meta.url)),
|
|
20
|
+
'utf8'
|
|
21
|
+
).trimEnd() + '\n'
|
|
22
|
+
|
|
23
|
+
/** @type {import('@7n/rules/scripts/lib/lint-surface/types.mjs').T0Pattern[]} */
|
|
24
|
+
export const patterns = [
|
|
25
|
+
{
|
|
26
|
+
id: 'js-run-jsconfig-create',
|
|
27
|
+
test: violations => violations.some(v => JSCONFIG_MISSING_RE.test(v.message)),
|
|
28
|
+
apply: (violations, ctx) => {
|
|
29
|
+
const cwd = ctx.cwd
|
|
30
|
+
const touchedFiles = []
|
|
31
|
+
for (const v of violations) {
|
|
32
|
+
const m = JSCONFIG_MISSING_WS_RE.exec(v.message)
|
|
33
|
+
if (!m) continue
|
|
34
|
+
const ws = m[1]
|
|
35
|
+
const target = join(cwd, ws, 'jsconfig.json')
|
|
36
|
+
if (!existsSync(target)) {
|
|
37
|
+
ctx.recordWrite?.(target)
|
|
38
|
+
writeFileSync(target, JSCONFIG_CONTENT, 'utf8')
|
|
39
|
+
touchedFiles.push(target)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (touchedFiles.length === 0) return { touchedFiles: [] }
|
|
43
|
+
return { touchedFiles, message: `створено jsconfig.json: ${touchedFiles.join(', ')}` }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
]
|
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
/** @see ./docs/runtime.md */
|
|
2
|
+
import { existsSync, statSync } from 'node:fs'
|
|
3
|
+
import { readFile } from 'node:fs/promises'
|
|
4
|
+
import { join, relative } from 'node:path'
|
|
5
|
+
|
|
6
|
+
import { findBunyanImportsInText, isBunyanScanSourceFile, shouldSkipFileForBunyanScan } from '../lib/bunyan-imports.mjs'
|
|
7
|
+
import { findUncheckedProcessEnvInText, isCheckEnvScanSourceFile } from '../lib/check-env-scan.mjs'
|
|
8
|
+
import { createViolationReporter } from '@7n/rules/scripts/lib/lint-surface/violation-reporter.mjs'
|
|
9
|
+
import { runConftestBatch } from '@7n/rules/scripts/lib/run-conftest-batch.mjs'
|
|
10
|
+
import { findConnFileRuleViolations, isConnFileRulesSourceFile } from '../lib/conn-file-rules.mjs'
|
|
11
|
+
import {
|
|
12
|
+
findConnFactoryImportsInText,
|
|
13
|
+
isConnImportsScanSourceFile,
|
|
14
|
+
isInsideConnDir,
|
|
15
|
+
resolveConnDirFromPackageJson
|
|
16
|
+
} from '../lib/conn-imports-scan.mjs'
|
|
17
|
+
import { loadCursorIgnorePaths } from '@7n/rules/scripts/lib/load-cursor-config.mjs'
|
|
18
|
+
import { findPromiseSetTimeoutInText, isPromiseSetTimeoutScanSourceFile } from '../lib/promise-settimeout-scan.mjs'
|
|
19
|
+
import { findTemporalUsageInText, isTemporalScanSourceFile } from '../lib/temporal-scan.mjs'
|
|
20
|
+
import { walkDir } from '@7n/rules/scripts/utils/walkDir.mjs'
|
|
21
|
+
import { getMonorepoPackageRootDirs } from '@7n/rules/scripts/lib/workspaces.mjs'
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Чи існує непорожній за змістом маркер каталогу `src/` (рекомендована структура js-run).
|
|
25
|
+
* @param {string} absPackageRoot абсолютний корінь пакета
|
|
26
|
+
* @returns {boolean} true, якщо `src/` існує і є каталогом
|
|
27
|
+
*/
|
|
28
|
+
function backendPackageHasSrcDir(absPackageRoot) {
|
|
29
|
+
const srcPath = join(absPackageRoot, 'src')
|
|
30
|
+
try {
|
|
31
|
+
return statSync(srcPath).isDirectory()
|
|
32
|
+
} catch {
|
|
33
|
+
return false
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* FS-existence + структурна валідація `jsconfig.json` у backend-пакеті з
|
|
39
|
+
* каталогом `src/`. Структуру (canonical `compilerOptions` і `include`)
|
|
40
|
+
* делегуємо у rego-пакет `js_run.jsconfig` через `runConftestBatch` — Plan B:
|
|
41
|
+
* Rego-authoritative, JS оркеструє per-package gate (frontend з `vite` сюди
|
|
42
|
+
* взагалі не доходить, бо викликається лише з backend-гілки).
|
|
43
|
+
* @param {string} rootDir відносний шлях workspace
|
|
44
|
+
* @param {string} absPackageRoot абсолютний корінь пакета
|
|
45
|
+
* @param {string} label префікс `[pkg] `
|
|
46
|
+
* @param {(msg: string) => void} fail callback для повідомлень про порушення
|
|
47
|
+
* @param {(msg: string) => void} passFn callback для повідомлень про успішну перевірку
|
|
48
|
+
* @returns {Promise<void>} результат
|
|
49
|
+
* @param {string} cwd корінь репозиторію
|
|
50
|
+
*/
|
|
51
|
+
async function checkBackendJsconfigWhenSrcPresent(rootDir, absPackageRoot, label, fail, passFn, cwd) {
|
|
52
|
+
if (!backendPackageHasSrcDir(absPackageRoot)) return
|
|
53
|
+
|
|
54
|
+
const jcPath = join(cwd, rootDir, 'jsconfig.json')
|
|
55
|
+
if (!existsSync(jcPath)) {
|
|
56
|
+
fail(
|
|
57
|
+
`${label}є каталог src/, але немає jsconfig.json — додай канонічний файл з js-run.mdc ` +
|
|
58
|
+
`(NodeNext, include: src/**/*).`
|
|
59
|
+
)
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
const violations = await runConftestBatch({
|
|
63
|
+
policyDirRel: 'js-run/jsconfig',
|
|
64
|
+
// Правило живе у плагіні поза вбудованим rules/ ядра — резолвимо policy-теку від себе.
|
|
65
|
+
policyDirAbs: new URL('../jsconfig/', import.meta.url).pathname,
|
|
66
|
+
namespace: 'js_run.jsconfig',
|
|
67
|
+
files: [jcPath]
|
|
68
|
+
})
|
|
69
|
+
if (violations.length === 0) {
|
|
70
|
+
passFn(`${label}jsconfig.json відповідає js_run.jsconfig (rego)`)
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
for (const v of violations) fail(`${label}${v.message}`)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Перетворює абсолютний шлях у posix-формі відносно кореня пакета.
|
|
78
|
+
* @param {string} absPackageRoot абсолютний корінь пакета
|
|
79
|
+
* @param {string} absPath абсолютний шлях до файлу
|
|
80
|
+
* @returns {string} відносний posix-шлях
|
|
81
|
+
*/
|
|
82
|
+
function relPosix(absPackageRoot, absPath) {
|
|
83
|
+
return relative(absPackageRoot, absPath).split('\\').join('/')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Сканує джерела пакета на заборонені імпорти `@nitra/bunyan` / `bunyan`.
|
|
88
|
+
* @param {string} absPackageRoot абсолютний шлях до кореня пакета
|
|
89
|
+
* @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
|
|
90
|
+
* @param {string} label префікс повідомлення `[<pkg>] `
|
|
91
|
+
* @param {(msg: string) => void} fail callback при помилці
|
|
92
|
+
* @returns {Promise<number>} кількість знайдених порушень
|
|
93
|
+
*/
|
|
94
|
+
async function checkBunyanImports(absPackageRoot, ignorePaths, label, fail) {
|
|
95
|
+
/** @type {string[]} */
|
|
96
|
+
const sourcePaths = []
|
|
97
|
+
await walkDir(
|
|
98
|
+
absPackageRoot,
|
|
99
|
+
absPath => {
|
|
100
|
+
const rel = relPosix(absPackageRoot, absPath)
|
|
101
|
+
if (!shouldSkipFileForBunyanScan(rel) && isBunyanScanSourceFile(rel)) {
|
|
102
|
+
sourcePaths.push(absPath)
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
ignorePaths
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
let violations = 0
|
|
109
|
+
for (const absPath of sourcePaths) {
|
|
110
|
+
const rel = relPosix(absPackageRoot, absPath)
|
|
111
|
+
const content = await readFile(absPath, 'utf8')
|
|
112
|
+
for (const v of findBunyanImportsInText(content, rel)) {
|
|
113
|
+
violations++
|
|
114
|
+
fail(`${label}${rel}:${v.line} — заміни '${v.module}' на '@nitra/pino': ${v.snippet}`)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return violations
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Збирає всі JS/TS-файли пакета (без node_modules, dist тощо).
|
|
122
|
+
* @param {string} absPackageRoot абсолютний шлях до кореня пакета
|
|
123
|
+
* @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
|
|
124
|
+
* @returns {Promise<string[]>} абсолютні шляхи до файлів
|
|
125
|
+
*/
|
|
126
|
+
async function collectSourceFiles(absPackageRoot, ignorePaths) {
|
|
127
|
+
/** @type {string[]} */
|
|
128
|
+
const out = []
|
|
129
|
+
await walkDir(
|
|
130
|
+
absPackageRoot,
|
|
131
|
+
absPath => {
|
|
132
|
+
const rel = relPosix(absPackageRoot, absPath)
|
|
133
|
+
if (isCheckEnvScanSourceFile(rel)) out.push(absPath)
|
|
134
|
+
},
|
|
135
|
+
ignorePaths
|
|
136
|
+
)
|
|
137
|
+
return out
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Перевіряє правило «Внутрішні аліаси» для пакета.
|
|
142
|
+
* @param {string} absPackageRoot абсолютний корінь пакета
|
|
143
|
+
* @param {string[]} sourcePaths абсолютні шляхи до файлів
|
|
144
|
+
* @param {unknown} pkgJson розпарсений `package.json` пакета (або null)
|
|
145
|
+
* @param {string} label префікс повідомлення `[<pkg>] `
|
|
146
|
+
* @param {(msg: string) => void} fail callback при помилці
|
|
147
|
+
* @returns {Promise<number>} кількість порушень
|
|
148
|
+
*/
|
|
149
|
+
async function checkConnImports(absPackageRoot, sourcePaths, pkgJson, label, fail) {
|
|
150
|
+
const connDir = resolveConnDirFromPackageJson(pkgJson)
|
|
151
|
+
let violations = 0
|
|
152
|
+
for (const absPath of sourcePaths) {
|
|
153
|
+
const rel = relPosix(absPackageRoot, absPath)
|
|
154
|
+
if (!isConnImportsScanSourceFile(rel)) continue
|
|
155
|
+
if (isInsideConnDir(rel, connDir)) continue
|
|
156
|
+
const content = await readFile(absPath, 'utf8')
|
|
157
|
+
for (const v of findConnFactoryImportsInText(content, rel)) {
|
|
158
|
+
violations++
|
|
159
|
+
const target = v.specifier === '*' ? `'${v.module}'` : `{ ${v.specifier} } from '${v.module}'`
|
|
160
|
+
fail(
|
|
161
|
+
`${label}${rel}:${v.line} — імпорт ${target} має бути в '${connDir}/' і реекспортуватися через '#conn/*': ${v.snippet}`
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return violations
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Перевіряє правила нейминга та експортів для файлів усередині `#conn/`.
|
|
170
|
+
*
|
|
171
|
+
* Канон імені: `ql-<id>` для GraphQL, `(pg|mysql|mssql)-(read|write)[-<id>]` для БД (js-run.mdc,
|
|
172
|
+
* розділ «Нейминг файлів у `src/conn/`»). Експорт у файлі — лише іменований, з імʼям, що
|
|
173
|
+
* дорівнює camelCase від basename файла (`pg-write-contract.js` → `export const pgWriteContract`).
|
|
174
|
+
* @param {string} absPackageRoot абсолютний корінь пакета
|
|
175
|
+
* @param {string[]} sourcePaths абсолютні шляхи до файлів пакета
|
|
176
|
+
* @param {unknown} pkgJson розпарсений package.json пакета (або null)
|
|
177
|
+
* @param {string} label префікс повідомлення `[<pkg>] `
|
|
178
|
+
* @param {(msg: string) => void} fail callback при помилці
|
|
179
|
+
* @returns {Promise<number>} кількість порушень
|
|
180
|
+
*/
|
|
181
|
+
async function checkConnFileNamingAndExports(absPackageRoot, sourcePaths, pkgJson, label, fail) {
|
|
182
|
+
const connDir = resolveConnDirFromPackageJson(pkgJson)
|
|
183
|
+
let violations = 0
|
|
184
|
+
for (const absPath of sourcePaths) {
|
|
185
|
+
const rel = relPosix(absPackageRoot, absPath)
|
|
186
|
+
if (!isConnFileToCheck(rel, connDir)) continue
|
|
187
|
+
const content = await readFile(absPath, 'utf8')
|
|
188
|
+
for (const v of findConnFileRuleViolations(content, rel)) {
|
|
189
|
+
violations++
|
|
190
|
+
fail(formatConnFileViolation(v, label, rel, connDir))
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return violations
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Чи `rel` — це conn-файл, який треба валідувати: під `connDir/`, з JS/TS-розширенням,
|
|
198
|
+
* не `index.*` (який є реекспортним барелем).
|
|
199
|
+
* @param {string} rel відносний шлях у posix-форматі
|
|
200
|
+
* @param {string} connDir каталог conn-файлів (наприклад `src/conn`)
|
|
201
|
+
* @returns {boolean} true, якщо файл потрібно перевірити
|
|
202
|
+
*/
|
|
203
|
+
function isConnFileToCheck(rel, connDir) {
|
|
204
|
+
if (!isInsideConnDir(rel, connDir)) return false
|
|
205
|
+
if (!isConnFileRulesSourceFile(rel)) return false
|
|
206
|
+
const base = rel.slice(rel.lastIndexOf('/') + 1)
|
|
207
|
+
return !base.startsWith('index.')
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Будує повідомлення про конкретне порушення canon-у файла з `connDir/`.
|
|
212
|
+
* @param {{ kind: 'name' | 'default-export' | 'export-name', expectedName?: string, foundNames?: string[] }} v опис порушення
|
|
213
|
+
* @param {string} label префікс повідомлення `[<pkg>] `
|
|
214
|
+
* @param {string} rel відносний шлях файла
|
|
215
|
+
* @param {string} connDir каталог conn-файлів
|
|
216
|
+
* @returns {string} повний текст повідомлення для `fail(...)`
|
|
217
|
+
*/
|
|
218
|
+
function formatConnFileViolation(v, label, rel, connDir) {
|
|
219
|
+
if (v.kind === 'name') {
|
|
220
|
+
return (
|
|
221
|
+
`${label}${rel} — назва файла в '${connDir}/' не відповідає канону js-run: ` +
|
|
222
|
+
`'ql-<id>', 'pg-{read|write}[-<id>]', 'mysql-{read|write}[-<id>]' або 'mssql-{read|write}[-<id>]' ` +
|
|
223
|
+
`(kebab-case, [a-z0-9-])`
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
if (v.kind === 'default-export') {
|
|
227
|
+
return `${label}${rel} — 'export default' заборонений у '${connDir}/'; зроби іменований експорт`
|
|
228
|
+
}
|
|
229
|
+
const found = v.foundNames?.length ? v.foundNames.join(', ') : '—'
|
|
230
|
+
return (
|
|
231
|
+
`${label}${rel} — очікується іменований експорт 'export const ${v.expectedName} = …' ` +
|
|
232
|
+
`(camelCase від назви файла); знайдено: ${found}`
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Перевіряє правило «CheckEnv» для пакета.
|
|
238
|
+
* @param {string} absPackageRoot абсолютний корінь пакета
|
|
239
|
+
* @param {string[]} sourcePaths абсолютні шляхи до файлів
|
|
240
|
+
* @param {string} label префікс повідомлення `[<pkg>] `
|
|
241
|
+
* @param {(msg: string) => void} fail callback при помилці
|
|
242
|
+
* @returns {Promise<number>} кількість порушень
|
|
243
|
+
*/
|
|
244
|
+
async function checkProcessEnvUsage(absPackageRoot, sourcePaths, label, fail) {
|
|
245
|
+
let violations = 0
|
|
246
|
+
for (const absPath of sourcePaths) {
|
|
247
|
+
const rel = relPosix(absPackageRoot, absPath)
|
|
248
|
+
const content = await readFile(absPath, 'utf8')
|
|
249
|
+
for (const v of findUncheckedProcessEnvInText(content, rel)) {
|
|
250
|
+
violations++
|
|
251
|
+
const message =
|
|
252
|
+
v.kind === 'process-env'
|
|
253
|
+
? `${label}${rel}:${v.line} — process.env.${v.name}: заміни на env з '@nitra/check-env' (обов'язкова змінна + checkEnv(['${v.name}'])) або з 'node:process' (опційна)`
|
|
254
|
+
: `${label}${rel}:${v.line} — env.${v.name} (з '@nitra/check-env') без checkEnv(['${v.name}']) (або '// @7n/rules ignore-next-line checkEnv' попереду)`
|
|
255
|
+
fail(message)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return violations
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Сканує джерела пакета на паттерн `new Promise(resolve => setTimeout(resolve, ms))`.
|
|
263
|
+
* @param {string} absPackageRoot абсолютний корінь пакета
|
|
264
|
+
* @param {string[]} sourcePaths абсолютні шляхи до файлів
|
|
265
|
+
* @param {string} label префікс повідомлення `[<pkg>] `
|
|
266
|
+
* @param {(msg: string) => void} fail callback при помилці
|
|
267
|
+
* @returns {Promise<number>} кількість порушень
|
|
268
|
+
*/
|
|
269
|
+
async function checkPromiseSetTimeoutPause(absPackageRoot, sourcePaths, label, fail) {
|
|
270
|
+
let violations = 0
|
|
271
|
+
for (const absPath of sourcePaths) {
|
|
272
|
+
const rel = relPosix(absPackageRoot, absPath)
|
|
273
|
+
if (!isPromiseSetTimeoutScanSourceFile(rel)) continue
|
|
274
|
+
const content = await readFile(absPath, 'utf8')
|
|
275
|
+
for (const v of findPromiseSetTimeoutInText(content, rel)) {
|
|
276
|
+
violations++
|
|
277
|
+
fail(
|
|
278
|
+
`${label}${rel}:${v.line} — заміни 'new Promise(r => setTimeout(r, ms))' на 'await setTimeout(ms)' з 'node:timers/promises': ${v.snippet}`
|
|
279
|
+
)
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return violations
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Сканує джерела пакета на `Temporal`, який у Bun runtime ще недоступний.
|
|
287
|
+
* @param {string} absPackageRoot абсолютний корінь пакета
|
|
288
|
+
* @param {string[]} sourcePaths абсолютні шляхи до файлів
|
|
289
|
+
* @param {string} label префікс повідомлення `[<pkg>] `
|
|
290
|
+
* @param {(msg: string) => void} fail callback при помилці
|
|
291
|
+
* @returns {Promise<number>} кількість порушень
|
|
292
|
+
*/
|
|
293
|
+
async function checkTemporalUsage(absPackageRoot, sourcePaths, label, fail) {
|
|
294
|
+
let violations = 0
|
|
295
|
+
for (const absPath of sourcePaths) {
|
|
296
|
+
const rel = relPosix(absPackageRoot, absPath)
|
|
297
|
+
if (!isTemporalScanSourceFile(rel)) continue
|
|
298
|
+
const content = await readFile(absPath, 'utf8')
|
|
299
|
+
for (const v of findTemporalUsageInText(content, rel)) {
|
|
300
|
+
violations++
|
|
301
|
+
fail(
|
|
302
|
+
`${label}${rel}:${v.line} — Temporal API заборонений у Bun runtime; використовуй Date або інʼєктований timestamp`
|
|
303
|
+
)
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return violations
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Перевіряє відповідність правилам js-run.mdc для одного workspace-пакета.
|
|
311
|
+
* @param {string} rootDir відносний шлях workspace (не `'.'`)
|
|
312
|
+
* @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
|
|
313
|
+
* @param {(msg: string) => void} fail функція зворотного виклику для реєстрації помилки перевірки
|
|
314
|
+
* @param {(msg: string) => void} passFn успішне повідомлення (як у check-reporter)
|
|
315
|
+
* @returns {Promise<void>} завершується після перевірок цього пакета
|
|
316
|
+
* @param {string} cwd корінь репозиторію
|
|
317
|
+
*/
|
|
318
|
+
async function checkWorkspacePackage(rootDir, ignorePaths, fail, passFn, cwd) {
|
|
319
|
+
const label = `[${rootDir}] `
|
|
320
|
+
const absPackageRoot = join(cwd, rootDir)
|
|
321
|
+
const pkgJson = await loadPackageJson(rootDir, cwd)
|
|
322
|
+
|
|
323
|
+
// Frontend-пакети (vite у devDependencies) виходять за межі js-run:
|
|
324
|
+
// браузерний бандл не має `node:process`, а `process.env.*` бандлер
|
|
325
|
+
// обробляє самостійно. Перевірку process.env / conn-аліасів пропускаємо;
|
|
326
|
+
// bunyan-залежність валідується в Rego (`npx @7n/rules fix`).
|
|
327
|
+
if (packageJsonHasViteDevDependency(pkgJson)) {
|
|
328
|
+
passFn(`${label}vite-пакет (frontend) — js-run пропущено (process.env / conn-aliases / OTEL configmap)`)
|
|
329
|
+
return
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
await checkBackendJsconfigWhenSrcPresent(rootDir, absPackageRoot, label, fail, passFn, cwd)
|
|
333
|
+
|
|
334
|
+
const importViolations = await checkBunyanImports(absPackageRoot, ignorePaths, label, fail)
|
|
335
|
+
if (importViolations === 0) {
|
|
336
|
+
passFn(`${label}немає імпортів '@nitra/bunyan' / 'bunyan' у джерелах`)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const sourcePaths = await collectSourceFiles(absPackageRoot, ignorePaths)
|
|
340
|
+
|
|
341
|
+
const connViolations = await checkConnImports(absPackageRoot, sourcePaths, pkgJson, label, fail)
|
|
342
|
+
if (connViolations === 0) {
|
|
343
|
+
const connDir = resolveConnDirFromPackageJson(pkgJson)
|
|
344
|
+
passFn(`${label}імпорти підключень (bun#SQL / mssql / @nitra/graphql-request#GraphQLClient) лише в '${connDir}/'`)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const connFileViolations = await checkConnFileNamingAndExports(absPackageRoot, sourcePaths, pkgJson, label, fail)
|
|
348
|
+
if (connFileViolations === 0) {
|
|
349
|
+
const connDir = resolveConnDirFromPackageJson(pkgJson)
|
|
350
|
+
passFn(
|
|
351
|
+
`${label}файли в '${connDir}/' дотримують канону js-run: нейминг (ql-/pg-/mysql-/mssql-…) і іменований експорт у camelCase від basename`
|
|
352
|
+
)
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const envViolations = await checkProcessEnvUsage(absPackageRoot, sourcePaths, label, fail)
|
|
356
|
+
if (envViolations === 0) {
|
|
357
|
+
passFn(
|
|
358
|
+
`${label}немає прямого process.env.*; усі env.* з '@nitra/check-env' закриті checkEnv(['…']) (або '// @7n/rules ignore-next-line checkEnv')`
|
|
359
|
+
)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const pauseViolations = await checkPromiseSetTimeoutPause(absPackageRoot, sourcePaths, label, fail)
|
|
363
|
+
if (pauseViolations === 0) {
|
|
364
|
+
passFn(`${label}немає 'new Promise(r => setTimeout(r, ms))' — паузи через 'node:timers/promises'`)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const temporalViolations = await checkTemporalUsage(absPackageRoot, sourcePaths, label, fail)
|
|
368
|
+
if (temporalViolations === 0) {
|
|
369
|
+
passFn(`${label}немає Temporal API у Bun runtime-коді`)
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
checkOtelConfigmap(rootDir, passFn, fail, cwd)
|
|
373
|
+
|
|
374
|
+
const connAliasViolations = checkConnAliasDeclaration(absPackageRoot, sourcePaths, pkgJson, label, fail)
|
|
375
|
+
if (connAliasViolations === 0) {
|
|
376
|
+
const connDir = resolveConnDirFromPackageJson(pkgJson)
|
|
377
|
+
passFn(`${label}package.json.imports["#conn/*"] оголошений (або файлів у '${connDir}/' немає)`)
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Чи має пакет `vite` у `devDependencies` (маркер frontend-пакета — vite/quasar/capacitor SPA).
|
|
383
|
+
* Семантично ідентично `packageJsonLacksViteDevDependency` з `auto-rules.mjs`, але
|
|
384
|
+
* приймає вже розпарсений pkgJson.
|
|
385
|
+
* @param {unknown} pkgJson розпарсений `package.json` пакета (або null)
|
|
386
|
+
* @returns {boolean} true, якщо `vite` присутній у `devDependencies`
|
|
387
|
+
*/
|
|
388
|
+
function packageJsonHasViteDevDependency(pkgJson) {
|
|
389
|
+
if (!pkgJson || typeof pkgJson !== 'object' || Array.isArray(pkgJson)) return false
|
|
390
|
+
const devDeps = /** @type {Record<string, unknown>} */ (pkgJson).devDependencies
|
|
391
|
+
if (!devDeps || typeof devDeps !== 'object' || Array.isArray(devDeps)) return false
|
|
392
|
+
return Object.hasOwn(devDeps, 'vite')
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Завантажує `package.json` пакета (якщо є). Заборону `@nitra/bunyan` / `bunyan`
|
|
397
|
+
* у dependencies/devDependencies перенесено в Rego (`npm/policy/js_run/package_json/`);
|
|
398
|
+
* `npx \@7n/rules check` запускає її по всіх workspace `package.json`. Тут лишилася
|
|
399
|
+
* лише AST-перевірка імпортів.
|
|
400
|
+
* @param {string} rootDir відносний шлях workspace
|
|
401
|
+
* @returns {Promise<unknown>} розпарсений package.json або null
|
|
402
|
+
* @param {string} cwd корінь репозиторію
|
|
403
|
+
*/
|
|
404
|
+
async function loadPackageJson(rootDir, cwd) {
|
|
405
|
+
const pkgPath = join(cwd, rootDir, 'package.json')
|
|
406
|
+
if (!existsSync(pkgPath)) return null
|
|
407
|
+
return JSON.parse(await readFile(pkgPath, 'utf8'))
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Перевіряє наявність `k8s/base/configmap.yaml` пакета. Структуру (наявність
|
|
412
|
+
* `OTEL_RESOURCE_ATTRIBUTES` з обов'язковими `service.name=` / `service.namespace=`)
|
|
413
|
+
* перенесено в Rego (`npm/policy/js_run/configmap/`); `npx \@7n/rules check`
|
|
414
|
+
* запускає її на всіх `k8s/base/configmap.yaml`.
|
|
415
|
+
* @param {string} rootDir відносний шлях workspace
|
|
416
|
+
* @param {(msg: string) => void} passFn успішне повідомлення
|
|
417
|
+
* @returns {void}
|
|
418
|
+
* @param {string} cwd корінь репозиторію
|
|
419
|
+
*/
|
|
420
|
+
/**
|
|
421
|
+
* Перевіряє наявність `package.json#imports["#conn/*"]` коли у `connDir` є файли.
|
|
422
|
+
* @param {string} absPackageRoot абсолютний корінь пакета
|
|
423
|
+
* @param {string[]} sourcePaths абсолютні шляхи до файлів пакета
|
|
424
|
+
* @param {unknown} pkgJson розпарсений package.json пакета (або null)
|
|
425
|
+
* @param {string} label префікс повідомлення
|
|
426
|
+
* @param {(msg: string) => void} fail callback при помилці
|
|
427
|
+
* @returns {number} 0 — ок, 1 — порушення
|
|
428
|
+
*/
|
|
429
|
+
function checkConnAliasDeclaration(absPackageRoot, sourcePaths, pkgJson, label, fail) {
|
|
430
|
+
const connDir = resolveConnDirFromPackageJson(pkgJson)
|
|
431
|
+
const hasConnFiles = sourcePaths.some(absPath => isInsideConnDir(relPosix(absPackageRoot, absPath), connDir))
|
|
432
|
+
if (!hasConnFiles) return 0
|
|
433
|
+
|
|
434
|
+
const imports =
|
|
435
|
+
pkgJson && typeof pkgJson === 'object' && !Array.isArray(pkgJson)
|
|
436
|
+
? /** @type {Record<string, unknown>} */ (pkgJson).imports
|
|
437
|
+
: null
|
|
438
|
+
if (imports && typeof imports === 'object' && !Array.isArray(imports) && imports['#conn/*']) return 0
|
|
439
|
+
|
|
440
|
+
fail(
|
|
441
|
+
`${label}є файли у '${connDir}/', але в package.json відсутній аліас "#conn/*" — ` +
|
|
442
|
+
`додай "imports": { "#conn/*": "./${connDir}/*" } (js-run.mdc conn-aliases)`
|
|
443
|
+
)
|
|
444
|
+
return 1
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Перевіряє наявність OTEL configmap для workspace-пакета.
|
|
449
|
+
* @param {string} rootDir корінь пакета відносно cwd
|
|
450
|
+
* @param {(msg: string) => void} passFn репортер успішної перевірки
|
|
451
|
+
* @param {(msg: string) => void} failFn репортер порушення
|
|
452
|
+
* @param {string} cwd робочий каталог
|
|
453
|
+
* @returns {void}
|
|
454
|
+
*/
|
|
455
|
+
function checkOtelConfigmap(rootDir, passFn, failFn, cwd) {
|
|
456
|
+
const k8sDir = join(cwd, rootDir, 'k8s')
|
|
457
|
+
if (!existsSync(k8sDir)) {
|
|
458
|
+
passFn(`${rootDir}: немає каталогу k8s/ — перевірку OTEL configmap пропущено`)
|
|
459
|
+
return
|
|
460
|
+
}
|
|
461
|
+
const configmapPath = join(k8sDir, 'base', 'configmap.yaml')
|
|
462
|
+
if (!existsSync(configmapPath)) {
|
|
463
|
+
failFn(
|
|
464
|
+
`${rootDir}/k8s/base/configmap.yaml відсутній — додай з полем OTEL_RESOURCE_ATTRIBUTES ` +
|
|
465
|
+
`(service.name=, service.namespace=), js-run.mdc`
|
|
466
|
+
)
|
|
467
|
+
return
|
|
468
|
+
}
|
|
469
|
+
passFn(`${rootDir}/k8s/base/configmap.yaml є (OTEL — npx @7n/rules fix → js_run.configmap)`)
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Перевіряє відповідність проєкту правилам js-run.mdc лише для workspace-пакетів (не корінь репо).
|
|
474
|
+
* @param {import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст лінту
|
|
475
|
+
* @returns {Promise<import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintResult>} перелік порушень
|
|
476
|
+
*/
|
|
477
|
+
export async function lint(ctx) {
|
|
478
|
+
const cwd = ctx.cwd
|
|
479
|
+
const reporter = createViolationReporter(ctx)
|
|
480
|
+
const { pass, fail } = reporter
|
|
481
|
+
|
|
482
|
+
const roots = await getMonorepoPackageRootDirs(cwd)
|
|
483
|
+
const workspaceRoots = roots.filter(r => r !== '.')
|
|
484
|
+
|
|
485
|
+
if (workspaceRoots.length === 0) {
|
|
486
|
+
pass('js-run: немає workspace-пакетів у кореневому package.json — перевірку залежностей і k8s у пакетах пропущено')
|
|
487
|
+
return reporter.result()
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const ignorePaths = await loadCursorIgnorePaths(cwd)
|
|
491
|
+
for (const r of workspaceRoots) {
|
|
492
|
+
await checkWorkspacePackage(r, ignorePaths, fail, pass, cwd)
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
return reporter.result()
|
|
496
|
+
}
|