@7n/rules-lang-js 0.3.1 → 0.4.0
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 +6 -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 +32 -0
- package/rules/js/knip/knip.mdc +15 -0
- package/rules/js/knip/main.mjs +68 -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,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: fix-package_json.mjs
|
|
4
|
+
resource: plugins/lang-js/rules/bun/package_json/fix-package_json.mjs
|
|
5
|
+
docgen:
|
|
6
|
+
crc: cf834366
|
|
7
|
+
model: openai-codex/gpt-5.4-mini
|
|
8
|
+
score: 100
|
|
9
|
+
issues: judge:inaccurate:0.97
|
|
10
|
+
judgeModel: openai-codex/gpt-5.4-mini
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Огляд
|
|
14
|
+
|
|
15
|
+
Файл знаходить `package.json` із порушеннями policy `bun/package_json` серед заборонених top-level полів і `scripts.lint` / `scripts.lint-*` та запускає T0-autofix для цих випадків. Він видаляє top-level поля за каноном `template/package.json.deny.json` і `package.json.deny.json`, а lint-скрипти переписує на канонічний `bunx n-rules lint ...` лише після репо-wide пошуку всіх їхніх викликів; нерозпізнані виклики лишаються без змін, і в такому разі скрипт не видаляється. Помилки обробляє fail-safe і не кидає назовні.
|
|
16
|
+
|
|
17
|
+
## Поведінка
|
|
18
|
+
|
|
19
|
+
1. `patterns` знаходить package.json, у яких є порушення policy для `bun/package_json`, і запускає T0-autofix лише для них.
|
|
20
|
+
2. Для кожного такого package.json вона орієнтується на канон deny-списку з `template/package.json.deny.json` і прибирає заборонені top-level поля.
|
|
21
|
+
3. Якщо в `package.json` є `scripts.lint` або `scripts.lint-*`, вона спершу шукає їхні виклики в репозиторії та переписує їх на канонічний `bunx n-rules lint ...`, щоб не зламати споживачів цих скриптів.
|
|
22
|
+
4. Власні скрипти цього ж `package.json` теж оновлюються до канонічного запуску, якщо вони посилаються на lint-скрипти, що плануються до видалення.
|
|
23
|
+
5. Скрипт видаляє `scripts.lint*` лише тоді, коли для них не лишається нерозпізнаних викликів; якщо десь є незрозумілий спосіб запуску, він лишає скрипт на місці й лише повідомляє про це.
|
|
24
|
+
6. Після змін вона записує оновлений `package.json` і повертає перелік зачеплених файлів та короткий підсумок змін; назовні помилки не пробиваються.
|
|
25
|
+
|
|
26
|
+
## Гарантії поведінки
|
|
27
|
+
|
|
28
|
+
- Перехоплює помилки і не пропускає винятків назовні (fail-safe).
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Directory Index
|
|
3
|
+
title: npm/rules/bun/package_json
|
|
4
|
+
resource: plugins/lang-js/rules/bun/package_json/
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
| Файл | Тип |
|
|
8
|
+
| ------------------------------------------- | --------- |
|
|
9
|
+
| [fix-package_json.mjs](fix-package_json.md) | JS Module |
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T0-autofix для policy-concern-а `bun/package_json`: видаляє заборонені top-level поля
|
|
3
|
+
* (канон — `template/package.json.deny.json`, той самий `data.template.deny`, що бачить rego)
|
|
4
|
+
* і `scripts.lint` / `scripts.lint-*` (bun.mdc — лінт лише через `n-rules lint`, не npm-скрипти).
|
|
5
|
+
*
|
|
6
|
+
* Просте видалення `scripts.lint*` небезпечне: якщо десь у репо (workflow yml, інший
|
|
7
|
+
* npm-скрипт) є виклик `bun run lint-js` тощо, видалення ключа зламає цей виклик.
|
|
8
|
+
* Тому перед видаленням шукаємо всі виклики кожного lint-скрипта репо-вайд і переписуємо
|
|
9
|
+
* їх на прямий `bunx n-rules lint <surface>` (canonical — той самий, що вимагають
|
|
10
|
+
* lint_js_yml/lint_style_yml). Скрипт видаляється лише якщо ВСІ його виклики вдалось
|
|
11
|
+
* переписати (файли, де виклик не розпізнано — залишаємо як є, скрипт теж лишається,
|
|
12
|
+
* щоб не зламати консьюмера мовчки).
|
|
13
|
+
*/
|
|
14
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
15
|
+
import { join } from 'node:path'
|
|
16
|
+
|
|
17
|
+
import { walkDir } from '@7n/rules/scripts/utils/walkDir.mjs'
|
|
18
|
+
import { loadCursorIgnorePaths } from '@7n/rules/scripts/lib/load-cursor-config.mjs'
|
|
19
|
+
|
|
20
|
+
const LINT_SCRIPT_RE = /^lint(-.*)?$/u
|
|
21
|
+
const WORKFLOW_YML_RE = /^\.github\/workflows\/.*\.ya?ml$/u
|
|
22
|
+
const LOCKFILE_RE = /(^|\/)(bun\.lockb?|package-lock\.json|yarn\.lock|pnpm-lock\.yaml)$/u
|
|
23
|
+
const PACKAGE_MANAGER_SCRIPT_PREFIX_RE = /\b(?:(?:bun|yarn|pnpm)(?:\s+run)?|npm\s+run)\s+/gu
|
|
24
|
+
const SCRIPT_NAME_CONTINUATION_RE = /[\w-]/u
|
|
25
|
+
|
|
26
|
+
// `lint-<suffix>` → rule-id канонічного `n-rules lint <rule-id>`; bare `lint` → без rule-id.
|
|
27
|
+
const SURFACE_MAP = { image: 'image-compress' }
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {string} scriptName напр. `lint-js`, `lint`
|
|
31
|
+
* @returns {string} суфікс аргументу для `n-rules lint` (порожній рядок для bare `lint`)
|
|
32
|
+
*/
|
|
33
|
+
function surfaceArgFor(scriptName) {
|
|
34
|
+
const suffix = scriptName.slice('lint'.length)
|
|
35
|
+
if (suffix.length === 0) return ''
|
|
36
|
+
const normalized = suffix.startsWith('-') ? suffix.slice(1) : suffix
|
|
37
|
+
return ` ${SURFACE_MAP[normalized] ?? normalized}`
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Знаходить діапазони виклику package-manager-ом заданого npm-скрипта: `bun[ run] X`,
|
|
42
|
+
* `yarn[ run] X`, `pnpm[ run] X`, `npm run X` (голий `npm X` — не валідний npm-синтаксис,
|
|
43
|
+
* окрім start/test). Right-boundary — НЕ `\b` (hyphen — non-word char, тож `lint\b`
|
|
44
|
+
* матчить всередині `lint-js`), а перевірка проти `[\w-]`, щоб `lint` не «зжирав»
|
|
45
|
+
* префікс `lint-js`.
|
|
46
|
+
* @param {string} content вміст для пошуку
|
|
47
|
+
* @param {string} scriptName ім'я npm-скрипта
|
|
48
|
+
* @returns {Array<[number, number]>} діапазони повного виклику в content
|
|
49
|
+
*/
|
|
50
|
+
function invocationRanges(content, scriptName) {
|
|
51
|
+
/** @type {Array<[number, number]>} */
|
|
52
|
+
const ranges = []
|
|
53
|
+
PACKAGE_MANAGER_SCRIPT_PREFIX_RE.lastIndex = 0
|
|
54
|
+
let match
|
|
55
|
+
while ((match = PACKAGE_MANAGER_SCRIPT_PREFIX_RE.exec(content))) {
|
|
56
|
+
const scriptStart = PACKAGE_MANAGER_SCRIPT_PREFIX_RE.lastIndex
|
|
57
|
+
if (!content.startsWith(scriptName, scriptStart)) continue
|
|
58
|
+
const scriptEnd = scriptStart + scriptName.length
|
|
59
|
+
if (SCRIPT_NAME_CONTINUATION_RE.test(content[scriptEnd] ?? '')) continue
|
|
60
|
+
ranges.push([match.index, scriptEnd])
|
|
61
|
+
}
|
|
62
|
+
return ranges
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Читає JSON-файл template-а з deny-полями концерну; відсутній/невалідний → {}.
|
|
67
|
+
* @param {string} path абсолютний шлях
|
|
68
|
+
* @returns {Record<string, string>} мапа `field -> reason`
|
|
69
|
+
*/
|
|
70
|
+
function readDenyTemplate(path) {
|
|
71
|
+
if (!existsSync(path)) return {}
|
|
72
|
+
try {
|
|
73
|
+
return JSON.parse(readFileSync(path, 'utf8'))
|
|
74
|
+
} catch {
|
|
75
|
+
return {}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* `workflow`/`package-json` — відомий, безпечно переписуваний формат; `other` — будь-який
|
|
81
|
+
* інший текстовий файл (Makefile, README, shell-скрипт…), де ми ЛИШЕ детектуємо виклик,
|
|
82
|
+
* але не переписуємо (немає надійного canonical-заміщення для довільного формату).
|
|
83
|
+
* @typedef {'workflow'|'package-json'|'other'} FileKind
|
|
84
|
+
*/
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @param {string} rel posix-відносний шлях від кореня репо
|
|
88
|
+
* @param {string} abs абсолютний шлях
|
|
89
|
+
* @param {string} rootPkgAbs абсолютний шлях кореневого package.json (сам він — не кандидат)
|
|
90
|
+
* @returns {FileKind|null} категорія файлу, або null якщо файл взагалі не кандидат (лок-файли)
|
|
91
|
+
*/
|
|
92
|
+
function classifyCandidate(rel, abs, rootPkgAbs) {
|
|
93
|
+
if (LOCKFILE_RE.test(rel)) return null
|
|
94
|
+
if (WORKFLOW_YML_RE.test(rel)) return 'workflow'
|
|
95
|
+
if (rel.endsWith('package.json')) return abs === rootPkgAbs ? null : 'package-json'
|
|
96
|
+
return 'other'
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Знаходить УСІ файли репозиторію (крім кореневого package.json і лок-файлів) як кандидатів
|
|
101
|
+
* на пошук викликів npm-скриптів — виклик може бути де завгодно (Makefile, README, CI,
|
|
102
|
+
* інший package.json), не лише у відомих форматах.
|
|
103
|
+
* @param {string} cwd абсолютний корінь репозиторію
|
|
104
|
+
* @param {string} rootPkgAbs абсолютний шлях кореневого package.json (виключити)
|
|
105
|
+
* @returns {Promise<Array<{ abs: string, kind: FileKind }>>} кандидати з категорією
|
|
106
|
+
*/
|
|
107
|
+
async function findUsageCandidateFiles(cwd, rootPkgAbs) {
|
|
108
|
+
const ignorePaths = await loadCursorIgnorePaths(cwd)
|
|
109
|
+
/** @type {Array<{ abs: string, kind: FileKind }>} */
|
|
110
|
+
const out = []
|
|
111
|
+
await walkDir(
|
|
112
|
+
cwd,
|
|
113
|
+
abs => {
|
|
114
|
+
const rel = abs
|
|
115
|
+
.slice(cwd.length + 1)
|
|
116
|
+
.split('\\')
|
|
117
|
+
.join('/')
|
|
118
|
+
const kind = classifyCandidate(rel, abs, rootPkgAbs)
|
|
119
|
+
if (kind) out.push({ abs, kind })
|
|
120
|
+
},
|
|
121
|
+
ignorePaths
|
|
122
|
+
)
|
|
123
|
+
return out
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Переписує один файл-кандидат: кожен знайдений виклик `scriptName` замінюється на
|
|
128
|
+
* канонічний `bunx n-rules lint<surface>` (workflow yml — з `--no-fix`, package.json
|
|
129
|
+
* scripts-чейни — без, дев-контекст хоче autofix).
|
|
130
|
+
* @param {string} content вміст файлу
|
|
131
|
+
* @param {string} scriptName ім'я lint-скрипта
|
|
132
|
+
* @param {boolean} isWorkflow чи це workflow yml (інакше — package.json)
|
|
133
|
+
* @returns {{ content: string, matched: boolean }} новий вміст і чи був матч
|
|
134
|
+
*/
|
|
135
|
+
function rewriteUsages(content, scriptName, isWorkflow) {
|
|
136
|
+
const ranges = invocationRanges(content, scriptName)
|
|
137
|
+
if (ranges.length === 0) return { content, matched: false }
|
|
138
|
+
const canonical = `bunx n-rules lint${surfaceArgFor(scriptName)}${isWorkflow ? ' --no-fix' : ''}`
|
|
139
|
+
let next = ''
|
|
140
|
+
let lastIndex = 0
|
|
141
|
+
for (const [start, end] of ranges) {
|
|
142
|
+
next += content.slice(lastIndex, start) + canonical
|
|
143
|
+
lastIndex = end
|
|
144
|
+
}
|
|
145
|
+
next += content.slice(lastIndex)
|
|
146
|
+
return { content: next, matched: true }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Для кожного lint-скрипта шукає й переписує його виклики по всіх кандидатах.
|
|
151
|
+
* `workflow`/`package-json` — переписуємо на канонічний `bunx n-rules lint`; `other`
|
|
152
|
+
* (Makefile, README, довільний shell) — лише детектуємо, без canonical-заміщення для
|
|
153
|
+
* довільного формату скрипт НЕ видаляємо (блокуючий, а не мовчки проігнорований, збіг).
|
|
154
|
+
* @param {string[]} scriptNames кандидати на видалення (`lint`, `lint-js`, …)
|
|
155
|
+
* @param {Array<{ abs: string, kind: FileKind }>} candidateFiles файли-кандидати з категорією
|
|
156
|
+
* @param {(absPath: string) => void} recordWrite реєстрація запису для rollback
|
|
157
|
+
* @returns {{ safeToRemove: Set<string>, touchedFiles: string[] }} скрипти без залишкових
|
|
158
|
+
* викликів (безпечно видалити) і список фактично змінених файлів
|
|
159
|
+
*/
|
|
160
|
+
function adaptUsages(scriptNames, candidateFiles, recordWrite) {
|
|
161
|
+
const unresolved = new Set()
|
|
162
|
+
const touchedFiles = []
|
|
163
|
+
const sortedNames = scriptNames.toSorted((a, b) => b.length - a.length)
|
|
164
|
+
|
|
165
|
+
for (const { abs, kind } of candidateFiles) {
|
|
166
|
+
if (kind === 'other') continue
|
|
167
|
+
const isWorkflow = kind === 'workflow'
|
|
168
|
+
let content
|
|
169
|
+
try {
|
|
170
|
+
content = readFileSync(abs, 'utf8')
|
|
171
|
+
} catch {
|
|
172
|
+
continue
|
|
173
|
+
}
|
|
174
|
+
let next = content
|
|
175
|
+
// Найдовші імена першими (defense-in-depth поряд із right-boundary lookahead) —
|
|
176
|
+
// `lint-js` перепишеться до того, як коротший `lint` встигне зачепити його префікс.
|
|
177
|
+
for (const name of sortedNames) next = rewriteUsages(next, name, isWorkflow).content
|
|
178
|
+
if (next === content) continue
|
|
179
|
+
recordWrite?.(abs)
|
|
180
|
+
writeFileSync(abs, next)
|
|
181
|
+
touchedFiles.push(abs)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Другий прохід (після переписів) — чи лишився десь виклик, який ми не розпізнали
|
|
185
|
+
// (`other`-формат або невідомий package-manager-синтаксис); такий скрипт НЕ видаляємо.
|
|
186
|
+
for (const { abs } of candidateFiles) {
|
|
187
|
+
let content
|
|
188
|
+
try {
|
|
189
|
+
content = readFileSync(abs, 'utf8')
|
|
190
|
+
} catch {
|
|
191
|
+
continue
|
|
192
|
+
}
|
|
193
|
+
for (const name of scriptNames) {
|
|
194
|
+
if (invocationRanges(content, name).length > 0) unresolved.add(name)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return { safeToRemove: new Set(scriptNames.filter(n => !unresolved.has(n))), touchedFiles }
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Адаптує виклики lint-скриптів в ІНШИХ скриптах ТОГО Ж package.json (напр. `precommit`:
|
|
203
|
+
* `bun run lint-js && bun test`) — цей файл не потрапляє у зовнішній `findUsageCandidateFiles`,
|
|
204
|
+
* бо він же ціль видалення. Мутує `pkg.scripts` на місці.
|
|
205
|
+
* @param {object} pkg розпарсений package.json (мутується)
|
|
206
|
+
* @param {string[]} scriptNames кандидати на видалення
|
|
207
|
+
* @returns {Set<string>} імена, для яких лишився нерозпізнаний виклик у власних скриптах
|
|
208
|
+
*/
|
|
209
|
+
function adaptOwnScripts(pkg, scriptNames) {
|
|
210
|
+
const unresolved = new Set()
|
|
211
|
+
if (!pkg.scripts || typeof pkg.scripts !== 'object') return unresolved
|
|
212
|
+
|
|
213
|
+
const sorted = scriptNames.toSorted((a, b) => b.length - a.length)
|
|
214
|
+
const otherEntries = Object.entries(pkg.scripts).filter(
|
|
215
|
+
([key, value]) => !scriptNames.includes(key) && typeof value === 'string'
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
for (const [key, value] of otherEntries) {
|
|
219
|
+
let next = value
|
|
220
|
+
for (const name of sorted) next = rewriteUsages(next, name, false).content
|
|
221
|
+
if (next !== value) pkg.scripts[key] = next
|
|
222
|
+
}
|
|
223
|
+
// Пере-перевірка після мутації: чи лишився десь нерозпізнаний виклик.
|
|
224
|
+
for (const [key] of otherEntries) {
|
|
225
|
+
const current = pkg.scripts[key]
|
|
226
|
+
if (typeof current !== 'string') continue
|
|
227
|
+
for (const name of scriptNames) {
|
|
228
|
+
if (invocationRanges(current, name).length > 0) unresolved.add(name)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return unresolved
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Видаляє заборонені top-level поля й `scripts.lint*` (лише ті, чиї виклики вже адаптовано)
|
|
236
|
+
* з `package.json`.
|
|
237
|
+
* @param {object} pkg розпарсений package.json
|
|
238
|
+
* @param {Record<string, string>} denyFields канон заборонених top-level полів
|
|
239
|
+
* @param {Set<string>} safeToRemove скрипти без залишкових зовнішніх викликів
|
|
240
|
+
* @returns {string[]} список видалених ключів (для message)
|
|
241
|
+
*/
|
|
242
|
+
function stripDenied(pkg, denyFields, safeToRemove) {
|
|
243
|
+
const removed = []
|
|
244
|
+
for (const field of Object.keys(denyFields)) {
|
|
245
|
+
if (!Object.hasOwn(pkg, field)) continue
|
|
246
|
+
delete pkg[field]
|
|
247
|
+
removed.push(field)
|
|
248
|
+
}
|
|
249
|
+
if (pkg.scripts && typeof pkg.scripts === 'object') {
|
|
250
|
+
for (const name of Object.keys(pkg.scripts)) {
|
|
251
|
+
if (!LINT_SCRIPT_RE.test(name) || !safeToRemove.has(name)) continue
|
|
252
|
+
delete pkg.scripts[name]
|
|
253
|
+
removed.push(`scripts.${name}`)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return removed
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** @type {import('@7n/rules/scripts/lib/lint-surface/types.mjs').T0Pattern[]} */
|
|
260
|
+
export const patterns = [
|
|
261
|
+
{
|
|
262
|
+
id: 'bun-package_json-strip-denied',
|
|
263
|
+
test: violations => violations.some(v => v.reason === 'policy-deny' && v.file),
|
|
264
|
+
apply: async (violations, ctx) => {
|
|
265
|
+
const denyFields = readDenyTemplate(join(ctx.concernDir ?? '', 'template', 'package.json.deny.json'))
|
|
266
|
+
const files = [...new Set(violations.filter(v => v.file).map(v => v.file))]
|
|
267
|
+
const touchedFiles = []
|
|
268
|
+
const messages = []
|
|
269
|
+
|
|
270
|
+
for (const rel of files) {
|
|
271
|
+
const abs = join(ctx.cwd, rel)
|
|
272
|
+
let pkg
|
|
273
|
+
try {
|
|
274
|
+
pkg = JSON.parse(readFileSync(abs, 'utf8'))
|
|
275
|
+
} catch {
|
|
276
|
+
continue
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const lintScriptNames =
|
|
280
|
+
pkg.scripts && typeof pkg.scripts === 'object'
|
|
281
|
+
? Object.keys(pkg.scripts).filter(n => LINT_SCRIPT_RE.test(n))
|
|
282
|
+
: []
|
|
283
|
+
|
|
284
|
+
let safeToRemove = new Set(lintScriptNames)
|
|
285
|
+
if (lintScriptNames.length > 0) {
|
|
286
|
+
const ownUnresolved = adaptOwnScripts(pkg, lintScriptNames)
|
|
287
|
+
const candidateFiles = await findUsageCandidateFiles(ctx.cwd, abs)
|
|
288
|
+
const adapted = adaptUsages(lintScriptNames, candidateFiles, ctx.recordWrite)
|
|
289
|
+
safeToRemove = new Set([...adapted.safeToRemove].filter(n => !ownUnresolved.has(n)))
|
|
290
|
+
touchedFiles.push(...adapted.touchedFiles)
|
|
291
|
+
const skipped = lintScriptNames.filter(n => !safeToRemove.has(n))
|
|
292
|
+
if (skipped.length > 0) {
|
|
293
|
+
messages.push(
|
|
294
|
+
`${rel}: не видаляю scripts.${skipped.join(', scripts.')} — знайдено нерозпізнаний виклик деінде`
|
|
295
|
+
)
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const removed = stripDenied(pkg, denyFields, safeToRemove)
|
|
300
|
+
if (removed.length === 0) continue
|
|
301
|
+
|
|
302
|
+
ctx.recordWrite?.(abs)
|
|
303
|
+
writeFileSync(abs, `${JSON.stringify(pkg, null, 2)}\n`)
|
|
304
|
+
touchedFiles.push(abs)
|
|
305
|
+
messages.push(`${rel}: -${removed.join(', -')}`)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return touchedFiles.length > 0 ? { touchedFiles, message: messages.join('; ') } : { touchedFiles: [] }
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
## Rego-gate: заборонені поля та devDependencies у кореневому package.json
|
|
2
|
+
|
|
3
|
+
Rego-пакет: `bun.package_json`
|
|
4
|
+
|
|
5
|
+
Цільовий файл: кореневий `package.json`.
|
|
6
|
+
|
|
7
|
+
Список заборонених top-level полів (template-driven): [package.json.deny.json](./template/package.json.deny.json)
|
|
8
|
+
|
|
9
|
+
Gate виносить два класи deny:
|
|
10
|
+
|
|
11
|
+
1. **Заборонені top-level поля** — будь-яке поле з `package.json.deny.json` присутнє у файлі (навіть із порожнім значенням `{}`).
|
|
12
|
+
2. **devDependencies не з білого списку** — дозволені лише `@nitra/*`/`@7n/*` та root-only тестові peer/tools (`vitest`, `@vitest/coverage-v8`, `@stryker-mutator/vitest-runner`, `@playwright/test`). Будь-який інший пакет → deny. CLI-тули, які `n-rules lint` спавнить через `bunx` (oxlint, jscpd, v8r, github-actionlint тощо), у root не пінуються — вони приїжджають як `dependencies` пакета `@7n/rules` (npm-module.mdc).
|
|
13
|
+
|
|
14
|
+
Перевірки, що потребують FS або cross-file контексту (наприклад наявність `yarn.lock`), лишаються у JS-шарі.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Перевірка кореневого `package.json` для bun (bun.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Канон надходить через --data: { "template": { "deny": ... } }
|
|
4
|
+
# Структура --data сформована з template/package.json.deny.json
|
|
5
|
+
# (top-level fields заборонені у root).
|
|
6
|
+
#
|
|
7
|
+
# Логіка, що ЛИШАЄТЬСЯ у rego (inverse-patterns, не виносяться у template):
|
|
8
|
+
# - `devDependencies` лише `@nitra/*` + root-only тестові peer/tools для `@7n/test coverage`
|
|
9
|
+
# (правило `test` enabled завжди — див. `test/auto.md`; published workspace-и не мають
|
|
10
|
+
# `devDependencies` за `npm-module.mdc`)
|
|
11
|
+
#
|
|
12
|
+
# Перевірки, які потребують FS / cross-file контексту, лишаються у JS.
|
|
13
|
+
package bun.package_json
|
|
14
|
+
|
|
15
|
+
import rego.v1
|
|
16
|
+
|
|
17
|
+
# ── deny: заборонені top-level поля (template-driven) ─────────────────────
|
|
18
|
+
|
|
19
|
+
# Сентинельний value відрізняє «поле відсутнє» від «поле є з будь-яким значенням»
|
|
20
|
+
# (наприклад `dependencies: {}` — присутнє але порожнє → теж заборонено).
|
|
21
|
+
deny contains msg if {
|
|
22
|
+
some field, reason in data.template.deny
|
|
23
|
+
object.get(input, field, "__bun_missing__") != "__bun_missing__"
|
|
24
|
+
msg := sprintf("package.json: поле %s — %s", [field, reason])
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# ── deny: scripts.lint / scripts.lint-* заборонені (bun.mdc lint) ────────
|
|
28
|
+
|
|
29
|
+
deny contains msg if {
|
|
30
|
+
is_object(input.scripts)
|
|
31
|
+
some script_name, _ in input.scripts
|
|
32
|
+
regex.match(`^lint(-.*)?$`, script_name)
|
|
33
|
+
msg := sprintf(
|
|
34
|
+
"package.json: scripts.%s заборонений — лінт запускається через n-rules lint, не через package.json-скрипти (bun.mdc)",
|
|
35
|
+
[script_name],
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# ── deny: devDependencies — лише `@nitra/*`/`@7n/*` + root-only тестові peer/tools ─
|
|
40
|
+
|
|
41
|
+
deny contains msg if {
|
|
42
|
+
is_object(input.devDependencies)
|
|
43
|
+
some name, _ in input.devDependencies
|
|
44
|
+
not allowed_root_dev_dependency(name)
|
|
45
|
+
msg := sprintf("Кореневі devDependencies: дозволені лише @nitra/*/@7n/* або root-only test peers — прибери або перенеси: %s (bun.mdc)", [name])
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# ── helpers ────────────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
# @stryker-mutator/core — обов'язковий exact-pin peer vitest-runner@9+ (раніше тягнувся транзитивно)
|
|
51
|
+
# @7n/test — оркестратор `coverage` (npx @7n/test coverage); devDependency, щоб npx резолвив
|
|
52
|
+
# локально без мережевого fetch щоразу.
|
|
53
|
+
allowed_root_test_deps := {"vitest", "@vitest/coverage-v8", "@stryker-mutator/vitest-runner", "@stryker-mutator/core", "@playwright/test", "@7n/test"}
|
|
54
|
+
|
|
55
|
+
allowed_root_dev_dependency(name) if {
|
|
56
|
+
startswith(name, "@nitra/")
|
|
57
|
+
} else if {
|
|
58
|
+
startswith(name, "@7n/")
|
|
59
|
+
} else if {
|
|
60
|
+
# Vitest/Stryker peer/tools + @7n/test (`coverage`) для `@7n/test coverage` живуть у корені
|
|
61
|
+
# будь-якого монорепо-споживача: правило `test` enabled завжди, а published workspace-и
|
|
62
|
+
# не мають `devDependencies` (`npm-module.mdc`).
|
|
63
|
+
name in allowed_root_test_deps
|
|
64
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
## `for...in` заборонено — рефакторити на `for...of`
|
|
2
|
+
|
|
3
|
+
Конструкція `for (const k in obj)` обходить успадковані ключі прототипу, тому майже завжди тягне `Object.hasOwn(obj, k)`-guard. Заборонена у `@nitra/eslint-config` через `no-restricted-syntax` для `ForInStatement` (з версії **3.8.0**). У каноні oxlint (`guard-for-in: "deny"` у [oxlint-canonical.json](../tooling/data/tooling/oxlint-canonical.json)) лишається `guard-for-in` як часткова страховка (oxlint не підтримує `no-restricted-syntax`) — `checkOxlintRc` у `check/main.mjs` звіряє `.oxlintrc.json` проєкту з цим каноном.
|
|
4
|
+
|
|
5
|
+
Замість цього обходь масив напряму, а об'єкт — через `Object.entries` / `Object.keys` / `Object.values`. У такому коді guard за `Object.hasOwn` стає непотрібним і має зникнути разом із `for...in`.
|
|
6
|
+
|
|
7
|
+
```javascript title="❌ до"
|
|
8
|
+
for (const k in obj) {
|
|
9
|
+
if (!Object.hasOwn(obj, k)) continue
|
|
10
|
+
use(k, obj[k])
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
for (const i in arr) {
|
|
14
|
+
use(arr[i])
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```javascript title="✅ після"
|
|
19
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
20
|
+
use(k, v)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
for (const item of arr) {
|
|
24
|
+
use(item)
|
|
25
|
+
}
|
|
26
|
+
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
|
|
3
|
+
"lint": {
|
|
4
|
+
"scope": "full",
|
|
5
|
+
"glob": [
|
|
6
|
+
"eslint.config.js",
|
|
7
|
+
"eslint.config.mjs",
|
|
8
|
+
"**/package.json",
|
|
9
|
+
".oxlintrc.json",
|
|
10
|
+
".github/workflows/lint-js.yml",
|
|
11
|
+
".github/workflows/lint.yml",
|
|
12
|
+
"knip.json",
|
|
13
|
+
".eslintrc",
|
|
14
|
+
".eslintrc.js",
|
|
15
|
+
".eslintrc.json",
|
|
16
|
+
".eslintrc.yml"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: eslint-config.mjs
|
|
4
|
+
resource: plugins/lang-js/rules/js/check/eslint-config.mjs
|
|
5
|
+
docgen:
|
|
6
|
+
crc: 6626a862
|
|
7
|
+
model: manual
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Огляд
|
|
11
|
+
|
|
12
|
+
Детекція воркспейс-типів (node/vue) репозиторію і чистий (read-only) планувальник
|
|
13
|
+
scaffold/merge для `eslint.config.js` consumer-репо. Спільне джерело правди для
|
|
14
|
+
detector-а `main.mjs` (перевірка, що кожен vue-воркспейс присутній у `vue: [...]`
|
|
15
|
+
аргументах `getConfig`) і T0-фіксера `fix-check.mjs` (детерміноване створення або
|
|
16
|
+
хірургічне оновлення конфігу — без повного перезапису файлу).
|
|
17
|
+
|
|
18
|
+
## Поведінка
|
|
19
|
+
|
|
20
|
+
- Тип воркспейсу визначається за root `package.json`: записи `workspaces`
|
|
21
|
+
(glob-и типу `packages/*` розгортаються у наявні директорії); воркспейс — vue,
|
|
22
|
+
якщо його `package.json` має `vue`/`nuxt` у deps або в ньому є хоч один `.vue`
|
|
23
|
+
файл (`node_modules`/`dist` ігноруються). Без `workspaces` класифікується сам
|
|
24
|
+
корінь як `.`.
|
|
25
|
+
- Merge наявного конфігу — хірургічний: додає відсутній `**/auto-imports.d.ts`
|
|
26
|
+
у перший `ignores: [...]`, вставляє відсутні vue-воркспейси у `vue: [...]`
|
|
27
|
+
(або нову властивість одразу після `getConfig({`), вилучає ці ж воркспейси з
|
|
28
|
+
`node: [...]`. Кастомні ignores, overrides і коментарі не змінюються.
|
|
29
|
+
- Fail-safe: якщо структура конфігу не розпізнається (немає `getConfig({`),
|
|
30
|
+
файл не чіпається — порушення лишається для ручного розгляду.
|
|
31
|
+
|
|
32
|
+
## Публічний API
|
|
33
|
+
|
|
34
|
+
- `detectWorkspaceTypes(cwd)` → `{ node: string[], vue: string[] }` — директорії
|
|
35
|
+
за типами.
|
|
36
|
+
- `parseVueList(raw)` → нормалізовані записи `vue: [...]` з тексту конфігу.
|
|
37
|
+
- `renderEslintConfigScaffold(types)` → повний шаблон `eslint.config.js`
|
|
38
|
+
(лише непорожні типи).
|
|
39
|
+
- `mergeEslintConfig(raw, types)` → новий вміст (`=== raw`, якщо merge
|
|
40
|
+
неможливий чи не потрібен).
|
|
41
|
+
- `planEslintConfigFix(cwd)` → `{ path, content, message } | null` — ідемпотентний
|
|
42
|
+
план для T0 (`null` на виправленому дереві).
|
|
43
|
+
- Константи reason-ів: `ESLINT_CONFIG_MISSING`, `ESLINT_CONFIG_IGNORES`,
|
|
44
|
+
`ESLINT_CONFIG_VUE_WORKSPACE`; `AUTO_IMPORTS_IGNORE`.
|
|
45
|
+
|
|
46
|
+
## Де використовується
|
|
47
|
+
|
|
48
|
+
- `npm/rules/js/check/main.mjs` — detector (reason-и порушень, перевірка
|
|
49
|
+
vue-воркспейсів).
|
|
50
|
+
- `npm/rules/js/check/fix-check.mjs` — T0-фіксер (виконання плану).
|
|
51
|
+
|
|
52
|
+
## Гарантії поведінки
|
|
53
|
+
|
|
54
|
+
- Read-only: модуль лише читає ФС і повертає план; запис виконує викликач.
|
|
55
|
+
- Ідемпотентність: повторний `planEslintConfigFix` на виправленому дереві → `null`.
|
|
56
|
+
- Биті/відсутні JSON-файли не кидають виняток (повертається `null`-об'єкт).
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: fix-check.mjs
|
|
4
|
+
resource: plugins/lang-js/rules/js/check/fix-check.mjs
|
|
5
|
+
docgen:
|
|
6
|
+
crc: d4e460e8
|
|
7
|
+
model: manual
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Огляд
|
|
11
|
+
|
|
12
|
+
T0-autofix для `js/check`: два детерміновані патерни, обидва без LLM.
|
|
13
|
+
|
|
14
|
+
- `js-check-eslint-config` — scaffold/merge `eslint.config.js` за планом
|
|
15
|
+
`planEslintConfigFix` (детекція воркспейс-типів node/vue). Раніше ці
|
|
16
|
+
порушення йшли у LLM-ладдер, який переписував конфіг цілком і вгадував типи
|
|
17
|
+
(інцидент: у vue-монорепо записано `getConfig({ node: ['npm'] })` — eslint
|
|
18
|
+
перестав обробляти .vue файли).
|
|
19
|
+
- `js-check-oxlintrc` — scaffold/merge `.oxlintrc.json` за планом
|
|
20
|
+
`planOxlintrcFix` (`../tooling/main.mjs`). Раніше ці порушення теж йшли у
|
|
21
|
+
LLM-ладдер: 15 КБ канону дешева модель не відтворює byte-perfect (verify
|
|
22
|
+
fail), а дорожча не встигає за один rung-таймаут.
|
|
23
|
+
|
|
24
|
+
## Поведінка
|
|
25
|
+
|
|
26
|
+
- `js-check-eslint-config` тригериться на reason-и `eslint-config-missing` /
|
|
27
|
+
`eslint-config-ignores` / `eslint-config-vue-workspace`. `apply` перераховує
|
|
28
|
+
план із поточного стану диска (ідемпотентно): відсутній конфіг —
|
|
29
|
+
створюється з детектованими типами; наявний — зазнає хірургічного merge, без
|
|
30
|
+
повного перезапису. Якщо план порожній (`null`) — жодних змін
|
|
31
|
+
(`touchedFiles: []`).
|
|
32
|
+
- `js-check-oxlintrc` тригериться на reason-и `oxlintrc-missing` /
|
|
33
|
+
`oxlintrc-drift` (`OXLINTRC_MISSING` / `OXLINTRC_DRIFT` з `../tooling/main.mjs`).
|
|
34
|
+
`apply` читає наявний `.oxlintrc.json` (`null`, якщо відсутній або невалідний
|
|
35
|
+
JSON) і канон, будує злитий обʼєкт через `planOxlintrcFix` і перезаписує
|
|
36
|
+
файл цілком (JSON, 2-space, з кінцевим переносом рядка).
|
|
37
|
+
- Решта порушень `js/check` (engines, workflows) — поза цим T0, стандартний
|
|
38
|
+
шлях (ladder/manual).
|
|
39
|
+
- Обидва патерни викликають `ctx.recordWrite` перед записом (pre-image для
|
|
40
|
+
central rollback).
|
|
41
|
+
|
|
42
|
+
## Гарантії поведінки
|
|
43
|
+
|
|
44
|
+
- `js-check-eslint-config` записує лише `eslint.config.js`/`eslint.config.mjs`
|
|
45
|
+
у корені репо; наявний конфіг ніколи не перезаписується цілком — тільки
|
|
46
|
+
точкові вставки/заміни.
|
|
47
|
+
- `js-check-oxlintrc` записує лише `.oxlintrc.json` у корені репо; результат
|
|
48
|
+
завжди проходить `verifyOxlintRcAgainstCanonical` без ручного втручання.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Directory Index
|
|
3
|
+
title: npm/rules/js/check
|
|
4
|
+
resource: plugins/lang-js/rules/js/check/
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# npm/rules/js/check
|
|
8
|
+
|
|
9
|
+
| Файл | Тип |
|
|
10
|
+
| ------------------------------------- | --------- |
|
|
11
|
+
| [eslint-config.mjs](eslint-config.md) | JS Module |
|
|
12
|
+
| [fix-check.mjs](fix-check.md) | JS Module |
|
|
13
|
+
| [main.mjs](main.md) | JS Module |
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: main.mjs
|
|
4
|
+
resource: plugins/lang-js/rules/js/check/main.mjs
|
|
5
|
+
docgen:
|
|
6
|
+
crc: 1da6a724
|
|
7
|
+
model: manual
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Огляд
|
|
11
|
+
|
|
12
|
+
Detector concern-а `js/check`: read-only перевірка відповідності проєкту правилам
|
|
13
|
+
`js.mdc`. Накопичує порушення через violation-reporter і повертає їх runner-у;
|
|
14
|
+
нічого не друкує і (за одним винятком — baseline `knip.json`) нічого не пише.
|
|
15
|
+
|
|
16
|
+
## Поведінка
|
|
17
|
+
|
|
18
|
+
- `eslint.config.js`/`eslint.config.mjs`: файл існує (інакше reason
|
|
19
|
+
`eslint-config-missing`), містить `getConfig` та імпорт `@nitra/eslint-config`,
|
|
20
|
+
в ignores є `**/auto-imports.d.ts` (reason `eslint-config-ignores`).
|
|
21
|
+
- Воркспейс-типи: кожен vue-воркспейс (детекція з `eslint-config.mjs` — root
|
|
22
|
+
`workspaces` + vue/nuxt-залежність або `.vue` файли) має бути у `vue: [...]`
|
|
23
|
+
аргументах `getConfig`, інакше reason `eslint-config-vue-workspace` — без
|
|
24
|
+
цього eslint не парсить `.vue` файли воркспейсу. Ці три reason-и детерміновано
|
|
25
|
+
виправляє T0 `fix-check.mjs`.
|
|
26
|
+
- Кожен workspace `package.json`: `"type": "module"`, `engines.node >= 24`,
|
|
27
|
+
`engines.bun >= 1.3` (кореневий `package.json` валідує Rego
|
|
28
|
+
`npm/policy/js_lint/package_json/`).
|
|
29
|
+
- `.oxlintrc.json`: існує, валідний JSON, збігається з каноном oxlint із пакета
|
|
30
|
+
`@7n/rules` (`verifyOxlintRcAgainstCanonical` з `../tooling/main.mjs`).
|
|
31
|
+
Відсутність — reason `oxlintrc-missing`, розходження з каноном — reason
|
|
32
|
+
`oxlintrc-drift` (константи `OXLINTRC_MISSING`/`OXLINTRC_DRIFT`); обидва
|
|
33
|
+
детерміновано виправляє T0 `fix-check.mjs` (патерн `js-check-oxlintrc`).
|
|
34
|
+
- `.github/workflows/lint-js.yml`: існує; `lint.yml` (якщо є) не дублює
|
|
35
|
+
oxlint/eslint/jscpd кроки.
|
|
36
|
+
- `knip.json`: якщо відсутній — копіюється канонічний baseline із пакета
|
|
37
|
+
(side effect, описаний у js.mdc); подальший вміст локально не валідується.
|
|
38
|
+
- Застарілі конфіги ESLint (`.eslintrc`, `.eslintrc.js`, `.eslintrc.json`,
|
|
39
|
+
`.eslintrc.yml`) — порушення: лише flat config.
|
|
40
|
+
|
|
41
|
+
## Публічний API
|
|
42
|
+
|
|
43
|
+
- `lint(ctx)` → `LintResult` — перелік порушень concern-а.
|
|
44
|
+
|
|
45
|
+
## Гарантії поведінки
|
|
46
|
+
|
|
47
|
+
- Read-only, крім єдиного side effect — створення `knip.json` з канону.
|
|
48
|
+
- Порушення повертаються reporter-ом, не друкуються.
|