@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,57 @@
|
|
|
1
|
+
## Rego-gate: валідація `npm/package.json`
|
|
2
|
+
|
|
3
|
+
Rego-пакет: `npm-module.npm_package_json`
|
|
4
|
+
|
|
5
|
+
Цільовий файл: `npm/package.json`
|
|
6
|
+
|
|
7
|
+
### Що перевіряється
|
|
8
|
+
|
|
9
|
+
**Поле `types`** (логіка в rego, не template-driven):
|
|
10
|
+
|
|
11
|
+
- Має відповідати патерну `./types/index.d.ts` або `./types/<назва>.d.ts|.d.mts`.
|
|
12
|
+
- Шляхи поза директорією `types/` або з іншими розширеннями (`.ts` без `.d`) — deny.
|
|
13
|
+
|
|
14
|
+
**Поле `files`** (частково template-driven):
|
|
15
|
+
|
|
16
|
+
- Обовʼязкове, має бути непорожнім масивом.
|
|
17
|
+
- Subset-of перевірка: кожне значення з канонічного сніпету має бути присутнє у `files`. За замовчуванням — `"types"` обовʼязковий.
|
|
18
|
+
|
|
19
|
+
**Поле `devDependencies`** (inverse-pattern, логіка в rego):
|
|
20
|
+
|
|
21
|
+
- Не публікуються користувачам пакета — має бути відсутнє або порожнє `{}`.
|
|
22
|
+
- Наявність будь-яких devDeps → deny з переліком залежностей. Dev-інструментарій переноситься у кореневий `package.json`; CLI-тули, які пакет спавнить через `bunx` у репозиторіях-споживачах (пінінг версій), — у `dependencies` (кореневе bun-правило `package_json` такі пакети в root devDeps не пускає).
|
|
23
|
+
|
|
24
|
+
Канонічний сніпет `files`: [package.json.snippet.json](./template/package.json.snippet.json)
|
|
25
|
+
|
|
26
|
+
FS-перевірки (наявність файлу зі шляху `types`, скан tarball на тест-патерни) — у JS-перевірці, не тут.
|
|
27
|
+
|
|
28
|
+
### Приклади
|
|
29
|
+
|
|
30
|
+
✓ Правильно:
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"name": "@7n/rules",
|
|
35
|
+
"types": "./types/bin/n-rules.d.ts",
|
|
36
|
+
"files": ["types", "mdc", "bin", "CHANGELOG.md"],
|
|
37
|
+
"dependencies": { "oxc-parser": "^0.128.0" }
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
✗ Неправильно — `types` поза директорією `types/`:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{ "types": "./dist/index.d.ts" }
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
✗ Неправильно — `files` без `"types"`:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{ "files": ["bin", "mdc"] }
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
✗ Неправильно — наявні `devDependencies`:
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{ "devDependencies": { "@7n/rules": "^1.0.0" } }
|
|
57
|
+
```
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Порт перевірок `npm/package.json` (npm-module.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Канон надходить через --data: { "template": { "snippet": ... } }
|
|
4
|
+
# Структура --data сформована з template/package.json.snippet.json
|
|
5
|
+
# (snippet-array subset-of для whitelist `files`).
|
|
6
|
+
#
|
|
7
|
+
# Логіка, що ЛИШАЄТЬСЯ у rego (inverse-patterns, не виносяться у template):
|
|
8
|
+
# - форма поля `types` (regex pattern: `./types/index.d.ts` або `./types/<…>.d.ts|.d.mts`);
|
|
9
|
+
# - `devDependencies` мають бути відсутні або порожні (inverse-pattern — заборона будь-яких).
|
|
10
|
+
#
|
|
11
|
+
# FS-перевірки (наявність файлу зі шляху `types`, скан tarball на тест-патерни) — у JS.
|
|
12
|
+
package npm_module.npm_package_json
|
|
13
|
+
|
|
14
|
+
import rego.v1
|
|
15
|
+
|
|
16
|
+
types_field_template := concat(" ", [
|
|
17
|
+
"npm/package.json: поле \"types\" має бути \"./types/index.d.ts\"",
|
|
18
|
+
"або \"./types/<…>.d.ts|.d.mts\" (зараз: %v) (npm-module.mdc)",
|
|
19
|
+
])
|
|
20
|
+
|
|
21
|
+
dev_deps_template := concat(" ", [
|
|
22
|
+
"npm/package.json: \"devDependencies\" не публікуються користувачам пакета —",
|
|
23
|
+
"dev-інструментарій перенеси у кореневий package.json, а CLI-тули, які пакет",
|
|
24
|
+
"спавнить через bunx у споживачів, — у \"dependencies\": %v (npm-module.mdc)",
|
|
25
|
+
])
|
|
26
|
+
|
|
27
|
+
# ── deny: types (regex — лишається в rego) ───────────────────────────────
|
|
28
|
+
|
|
29
|
+
deny contains msg if {
|
|
30
|
+
types_field := object.get(input, "types", "")
|
|
31
|
+
not valid_types_field(types_field)
|
|
32
|
+
msg := sprintf(types_field_template, [types_field])
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# ── deny: files має існувати та бути масивом ─────────────────────────────
|
|
36
|
+
|
|
37
|
+
deny contains msg if {
|
|
38
|
+
not is_array(object.get(input, "files", null))
|
|
39
|
+
msg := "npm/package.json: обовʼязковий whitelist \"files\" (без нього npm пакує майже все) (npm-module.mdc)"
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
deny contains msg if {
|
|
43
|
+
is_array(input.files)
|
|
44
|
+
count(input.files) == 0
|
|
45
|
+
msg := "npm/package.json: масив \"files\" не повинен бути порожнім (npm-module.mdc: компактний пакет)"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# ── deny: files subset-of з template (template-driven) ──────────────────
|
|
49
|
+
|
|
50
|
+
deny contains msg if {
|
|
51
|
+
some field, expected_values in data.template.snippet
|
|
52
|
+
is_array(object.get(input, field, null))
|
|
53
|
+
count(input[field]) > 0
|
|
54
|
+
actual_set := {v | some v in input[field]}
|
|
55
|
+
some required in expected_values
|
|
56
|
+
not required in actual_set
|
|
57
|
+
msg := sprintf("npm/package.json: масив \"%s\" має містити %q (npm-module.mdc)", [field, required])
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# ── deny: devDependencies (inverse-pattern, лишається в rego) ────────────
|
|
61
|
+
|
|
62
|
+
deny contains msg if {
|
|
63
|
+
dev := object.get(input, "devDependencies", {})
|
|
64
|
+
count(dev) > 0
|
|
65
|
+
names := concat(", ", sort([n | some n, _ in dev]))
|
|
66
|
+
msg := sprintf(dev_deps_template, [names])
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# ── helpers ────────────────────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
valid_types_field("./types/index.d.ts")
|
|
72
|
+
|
|
73
|
+
valid_types_field(t) if regex.match(`^\./types/.+\.d\.(ts|mts)$`, t)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "files": ["types"] }
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: main.mjs
|
|
4
|
+
resource: plugins/lang-js/rules/npm-module/package_structure/main.mjs
|
|
5
|
+
docgen:
|
|
6
|
+
crc: b6884b16
|
|
7
|
+
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
8
|
+
score: 85
|
|
9
|
+
issues: anchor-miss:(npm-module.mdc),surzhik,judge:inaccurate:0.99
|
|
10
|
+
judgeModel: openai-codex/gpt-5.4-mini
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Огляд
|
|
14
|
+
|
|
15
|
+
Компонент валідує відповідність проєкту стандартам, визначеним у `npm-module.mdc`, аналізуючи конфігурації (`tsconfig.emit-types.json`, `package.json`, `meta.json`). Він виконує логіку, зокрема через функції `globToRegex`, `findTestFrameworkImport`, `classifyPublishedFileAsTest` та `main`, обробляючи помилки за принципом fail-safe, ніколи не кидаючи винятків назовні. Процес роботи є read-only, і він свідомо ігнорує каталоги `.github` та `.git` під час аналізу.
|
|
16
|
+
|
|
17
|
+
## Поведінка
|
|
18
|
+
|
|
19
|
+
Поведінка:
|
|
20
|
+
globToRegex перетворює glob-патерн у регулярний вираз з якорями початку та кінця, підтримуючи globstar, `*`, `?` та brace-альтернативи; ігнорує шляхи з `.github` та `.git`.
|
|
21
|
+
findTestFrameworkImport шукає у вмісті файлу імпорти або виклики, які вказують на використання тестових фреймворків.
|
|
22
|
+
classifyPublishedFileAsTest визначає, чи вважається певний опублікований файл тестовим або фікстурою на основі назви, каталогу або імпортів.
|
|
23
|
+
main виконує повну перевірку відповідності проєкту конвенціям npm-module.mdc, аналізуючи структуру, конфігурації та файли, і виводить статус проходження перевірок.
|
|
24
|
+
|
|
25
|
+
## Публічний API
|
|
26
|
+
|
|
27
|
+
globToRegex — перетворює шаблон пошуку файлів у регулярний вираз, забезпечуючи коректне зіставлення шляхів. Реалізація живе в ядрі (`@7n/rules/scripts/lib/glob-to-regex.mjs`, спільна з auto-rules) — тут ре-експорт.
|
|
28
|
+
findTestFrameworkImport — виявляє, чи використовується у файлі механізм імпорту тест-фреймворку.
|
|
29
|
+
classifyPublishedFileAsTest — визначає, чи повинен вважатися опублікований файл тестом або фікстурою, ґрунтуючись на його шляху та вмісті.
|
|
30
|
+
main — забезпечує дотримання проєкту всіх вимог, визначених у конвенціях, описаних у npm-module.mdc.
|
|
31
|
+
|
|
32
|
+
## Гарантії поведінки
|
|
33
|
+
|
|
34
|
+
- Read-only: не виконує операцій запису (ФС/БД).
|
|
35
|
+
- Перехоплює помилки і не пропускає винятків назовні (fail-safe).
|
|
36
|
+
- За певних помилок повертає порожнє значення (напр. `null`) замість винятку.
|
|
37
|
+
- Свідомо пропускає шляхи: `.github`, `.git`.
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
/** @see ./docs/package_structure.md */
|
|
2
|
+
import { existsSync } from 'node:fs'
|
|
3
|
+
import { readFile, stat } from 'node:fs/promises'
|
|
4
|
+
import { join, sep } from 'node:path'
|
|
5
|
+
|
|
6
|
+
// glob → RegExp живе в ядрі (спільний з auto-rules рушієм); ре-експорт зберігає публічний API концерну
|
|
7
|
+
export { globToRegex } from '@7n/rules/scripts/lib/glob-to-regex.mjs'
|
|
8
|
+
import { globToRegex } from '@7n/rules/scripts/lib/glob-to-regex.mjs'
|
|
9
|
+
|
|
10
|
+
import { parseSync } from 'oxc-parser'
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
dynamicImportModule,
|
|
14
|
+
langFromPath,
|
|
15
|
+
requireCallModule,
|
|
16
|
+
walkAstWithAncestors
|
|
17
|
+
} from '@7n/rules/scripts/utils/ast-scan-utils.mjs'
|
|
18
|
+
import { createViolationReporter } from '@7n/rules/scripts/lib/lint-surface/violation-reporter.mjs'
|
|
19
|
+
import { loadCursorIgnorePaths } from '@7n/rules/scripts/lib/load-cursor-config.mjs'
|
|
20
|
+
import { walkDir } from '@7n/rules/scripts/utils/walkDir.mjs'
|
|
21
|
+
|
|
22
|
+
/** Файл проєкту TypeScript для emit без каталогу `src` (див. npm-module.mdc) */
|
|
23
|
+
const EMIT_TYPES_CONFIG = 'npm/tsconfig.emit-types.json'
|
|
24
|
+
|
|
25
|
+
/** Каталоги, які за конвенцією тримають тести / фікстури і не повинні публікуватися. */
|
|
26
|
+
const TEST_DIR_NAMES = new Set(['tests', '__tests__', 'fixtures', '__fixtures__', 'spec', 'test'])
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Імена файлів за патернами test/spec (тільки basename, без path). Rego
|
|
30
|
+
* (`*_test.rego`) свідомо не входить: за конвенцією conftest юніт-тест лежить
|
|
31
|
+
* поруч з полісі у тому самому `package` — і це дозволений виняток усередині
|
|
32
|
+
* опублікованого `policy/`-каталогу (npm-module.mdc).
|
|
33
|
+
* @param {string} cwd корінь репозиторію
|
|
34
|
+
*/
|
|
35
|
+
const TEST_FILE_PATTERNS = [/^.+\.(test|spec)\.[cm]?[jt]sx?$/iu]
|
|
36
|
+
|
|
37
|
+
/** Розширення, у яких ловимо імпорти test-фреймворків. */
|
|
38
|
+
const JS_LIKE_EXT_RE = /\.[cm]?[jt]sx?$/iu
|
|
39
|
+
|
|
40
|
+
/** Імпорти/require/dynamic-import, які видають test-файл. */
|
|
41
|
+
const TEST_FRAMEWORK_MODULES = new Set([
|
|
42
|
+
'bun:test',
|
|
43
|
+
'node:test',
|
|
44
|
+
'vitest',
|
|
45
|
+
'@jest/globals',
|
|
46
|
+
'jest',
|
|
47
|
+
'mocha',
|
|
48
|
+
'ava',
|
|
49
|
+
'tap',
|
|
50
|
+
'tape'
|
|
51
|
+
])
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Чи є під `npm/src` хоча б один `.js` (рекурсивно).
|
|
55
|
+
* @param {string} cwd корінь репозиторію
|
|
56
|
+
* @param {string[]} [ignorePaths] абсолютні шляхи каталогів, повністю виключених з обходу
|
|
57
|
+
* @returns {Promise<boolean>} `true`, якщо знайдено хоча б один `.js`
|
|
58
|
+
*/
|
|
59
|
+
async function npmSrcTreeHasJsFile(cwd, ignorePaths = []) {
|
|
60
|
+
const root = join(cwd, 'npm/src')
|
|
61
|
+
if (!existsSync(root)) {
|
|
62
|
+
return false
|
|
63
|
+
}
|
|
64
|
+
let found = false
|
|
65
|
+
await walkDir(
|
|
66
|
+
root,
|
|
67
|
+
p => {
|
|
68
|
+
if (p.endsWith('.js')) {
|
|
69
|
+
found = true
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
ignorePaths
|
|
73
|
+
)
|
|
74
|
+
return found
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Знаходить текстовий вміст конфігурації hk для перевірки npm-module.
|
|
79
|
+
* @returns {Promise<{ path: string, text: string } | null>} знайдений файл або `null`
|
|
80
|
+
* @param {string} cwd корінь репозиторію
|
|
81
|
+
*/
|
|
82
|
+
async function readHkConfig(cwd) {
|
|
83
|
+
const candidates = ['hk.pkl', '.config/hk.pkl']
|
|
84
|
+
for (const p of candidates) {
|
|
85
|
+
const abs = join(cwd, p)
|
|
86
|
+
if (existsSync(abs)) {
|
|
87
|
+
const text = await readFile(abs, 'utf8')
|
|
88
|
+
return { path: p, text }
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return null
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Підрядки для hk при layout з каталогом `npm/src` і glob `src` + `.js` у команді (див. npm-module.mdc).
|
|
96
|
+
* @param {string} hkText текст конфігурації hk
|
|
97
|
+
* @returns {string[]} відсутні фрагменти
|
|
98
|
+
*/
|
|
99
|
+
function missingHkSrcLayoutFragments(hkText) {
|
|
100
|
+
const need = [
|
|
101
|
+
'["pre-commit"]',
|
|
102
|
+
'bunx -p typescript tsc',
|
|
103
|
+
'src/**/*.js',
|
|
104
|
+
'--declaration',
|
|
105
|
+
'--allowJs',
|
|
106
|
+
'--emitDeclarationOnly',
|
|
107
|
+
'--outDir types',
|
|
108
|
+
'--skipLibCheck'
|
|
109
|
+
]
|
|
110
|
+
return need.filter(s => !hkText.includes(s))
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Підрядки для hk при layout з `tsconfig.emit-types.json` (див. npm-module.mdc).
|
|
115
|
+
* @param {string} hkText текст конфігурації hk
|
|
116
|
+
* @returns {string[]} відсутні фрагменти
|
|
117
|
+
*/
|
|
118
|
+
function missingHkEmitTypesConfigFragments(hkText) {
|
|
119
|
+
const need = ['["pre-commit"]', 'bunx -p typescript tsc', 'tsconfig.emit-types.json']
|
|
120
|
+
return need.filter(s => !hkText.includes(s))
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Застаріла інвокація команди `check`, прибраної в v14 (уніфікована поверхня `lint`).
|
|
125
|
+
* Матч на підрядок "check changelog" незалежно від того, яким бінарем/шляхом викликано.
|
|
126
|
+
*/
|
|
127
|
+
const DEPRECATED_CHECK_CHANGELOG_RE = /\bcheck\s+changelog\b/u
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Підрядки канонічного кроку `npm-changelog` у hk.pkl (fix-only, autofix через env
|
|
131
|
+
* `N_RULES_CHANGELOG_AUTOFIX=1`; сам крок ставить change-файл у git-індекс, `stage`
|
|
132
|
+
* не потрібен). Див. npm-module.mdc.
|
|
133
|
+
* @param {string} hkText текст конфігурації hk
|
|
134
|
+
* @returns {string[]} відсутні фрагменти
|
|
135
|
+
*/
|
|
136
|
+
function missingHkNpmChangelogFragments(hkText) {
|
|
137
|
+
const need = ['["npm-changelog"]', 'N_RULES_CHANGELOG_AUTOFIX=1', 'lint changelog']
|
|
138
|
+
return need.filter(s => !hkText.includes(s))
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Шлях на дискі до файлу з поля `types` у `npm/package.json` (значення на кшталт `./types/bin/x.d.ts`).
|
|
143
|
+
* @param {string} typesField значення поля `types` з `package.json`
|
|
144
|
+
* @returns {string | null} абсолютний шлях або `null`
|
|
145
|
+
*/
|
|
146
|
+
function npmTypesFileFromPackageField(typesField) {
|
|
147
|
+
if (typeof typesField !== 'string' || !typesField.startsWith('./types/')) {
|
|
148
|
+
return null
|
|
149
|
+
}
|
|
150
|
+
const rel = typesField.slice(2)
|
|
151
|
+
return join('npm', rel)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Перевіряє наявність на диску файлу зі значення `types` у `npm/package.json`
|
|
156
|
+
* (cross-file: JSON-поле + FS). Структуру самого поля валідує
|
|
157
|
+
* `npm/policy/npm_module/npm_package_json/`; тут — лише чи файл реально існує.
|
|
158
|
+
* @param {boolean} useSrcJsLayout чи використовується layout з npm/src
|
|
159
|
+
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
160
|
+
* @param {(msg: string) => void} failFn callback при помилці
|
|
161
|
+
* @param {string} cwd корінь репозиторію
|
|
162
|
+
*/
|
|
163
|
+
async function checkNpmPackageJson(useSrcJsLayout, passFn, failFn, cwd) {
|
|
164
|
+
const npmPkgPath = join(cwd, 'npm/package.json')
|
|
165
|
+
if (!existsSync(npmPkgPath)) return
|
|
166
|
+
const npmPkg = JSON.parse(await readFile(npmPkgPath, 'utf8'))
|
|
167
|
+
const typesField = npmPkg.types
|
|
168
|
+
|
|
169
|
+
const typesRel = useSrcJsLayout ? join('npm', 'types', 'index.d.ts') : npmTypesFileFromPackageField(typesField)
|
|
170
|
+
const missingTypesMsg = useSrcJsLayout
|
|
171
|
+
? `Відсутній ${join('npm', 'types', 'index.d.ts')} (згенеруй tsc з npm-module.mdc)`
|
|
172
|
+
: `Файл для поля types не знайдено або шлях не під ./types/ — ${String(typesField)}`
|
|
173
|
+
if (typesRel && existsSync(join(cwd, typesRel))) {
|
|
174
|
+
passFn(`${typesRel} існує`)
|
|
175
|
+
} else {
|
|
176
|
+
failFn(missingTypesMsg)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* FS-existence для `npm/tsconfig.emit-types.json` (структуру `compilerOptions`
|
|
182
|
+
* валідує `npm/policy/npm_module/emit_types_config/`).
|
|
183
|
+
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
184
|
+
* @param {(msg: string) => void} failFn callback при помилці
|
|
185
|
+
* @param {string} cwd корінь репозиторію
|
|
186
|
+
*/
|
|
187
|
+
function checkEmitTypesConfig(passFn, failFn, cwd) {
|
|
188
|
+
if (!existsSync(join(cwd, EMIT_TYPES_CONFIG))) {
|
|
189
|
+
failFn(
|
|
190
|
+
`Без .js під npm/src потрібен ${EMIT_TYPES_CONFIG} (див. npm-module.mdc: emit через tsconfig, без штучного src/index.js)`
|
|
191
|
+
)
|
|
192
|
+
return
|
|
193
|
+
}
|
|
194
|
+
passFn(`${EMIT_TYPES_CONFIG} є (структуру перевіряє npx @7n/rules fix → npm_module.emit_types_config)`)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* FS-existence для `npm-publish.yml` workflow. Поля workflow (`on.push.paths`,
|
|
199
|
+
* `branches`, `id-token: write`, JS-DevTools/npm-publish step) валідує
|
|
200
|
+
* `npm/policy/npm_module/npm_publish_yml/`.
|
|
201
|
+
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
202
|
+
* @param {(msg: string) => void} failFn callback при виявленому порушенні
|
|
203
|
+
* @param {string} cwd корінь репозиторію
|
|
204
|
+
*/
|
|
205
|
+
function checkPublishWorkflow(passFn, failFn, cwd) {
|
|
206
|
+
const publishWf = '.github/workflows/npm-publish.yml'
|
|
207
|
+
if (existsSync(join(cwd, publishWf))) {
|
|
208
|
+
passFn(`${publishWf} є (структуру перевіряє npx @7n/rules fix → npm_module.npm_publish_yml)`)
|
|
209
|
+
} else {
|
|
210
|
+
failFn(`Відсутній ${publishWf} (npm-module.mdc: npm publish)`)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Збирає список файлів, що потраплять у tarball, виходячи з `files` у
|
|
216
|
+
* `npm/package.json`. Підтримує позитивні patterns (директорії або файли) і
|
|
217
|
+
* негативні (`!…`). Шляхи повертаються у posix-формі, відносно `npm/`.
|
|
218
|
+
* Не пробує дублювати всю логіку `npm pack` (license/readme/тощо) — тут лише
|
|
219
|
+
* простір імен `files`, бо саме його сканує check.
|
|
220
|
+
* @param {string[]} filesField значення поля `files`
|
|
221
|
+
* @returns {Promise<string[]>} відсортовані posix-шляхи без `npm/` префікса
|
|
222
|
+
* @param {string} cwd корінь репозиторію
|
|
223
|
+
*/
|
|
224
|
+
async function collectPublishedFiles(filesField, cwd) {
|
|
225
|
+
const positives = filesField.filter(p => typeof p === 'string' && !p.startsWith('!'))
|
|
226
|
+
const negatives = filesField.filter(p => typeof p === 'string' && p.startsWith('!')).map(p => globToRegex(p.slice(1)))
|
|
227
|
+
/** @type {Set<string>} */
|
|
228
|
+
const collected = new Set()
|
|
229
|
+
const npmRoot = join(cwd, 'npm')
|
|
230
|
+
for (const entry of positives) {
|
|
231
|
+
const fullPath = join(npmRoot, entry)
|
|
232
|
+
if (!existsSync(fullPath)) continue
|
|
233
|
+
const s = await stat(fullPath)
|
|
234
|
+
if (s.isFile()) {
|
|
235
|
+
collected.add(entry)
|
|
236
|
+
continue
|
|
237
|
+
}
|
|
238
|
+
if (!s.isDirectory()) continue
|
|
239
|
+
await walkDir(fullPath, p => {
|
|
240
|
+
const rel = p
|
|
241
|
+
.slice(npmRoot.length + 1)
|
|
242
|
+
.split(sep)
|
|
243
|
+
.join('/')
|
|
244
|
+
collected.add(rel)
|
|
245
|
+
})
|
|
246
|
+
}
|
|
247
|
+
const filtered = [...collected].filter(rel => negatives.every(re => !re.test(rel)))
|
|
248
|
+
filtered.sort()
|
|
249
|
+
return filtered
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Чи є у файлі імпорт/require/dynamic-import з модуля тест-фреймворку.
|
|
254
|
+
* Парсимо через oxc-parser (як `bunyan-imports`/`redis-imports`). При помилці
|
|
255
|
+
* парсингу повертаємо `null` — це не наш checker для синтаксису.
|
|
256
|
+
* @param {string} content повний текст файлу
|
|
257
|
+
* @param {string} virtualPath шлях для вибору `lang`
|
|
258
|
+
* @returns {string | null} модуль, через який видно тест, або `null`
|
|
259
|
+
*/
|
|
260
|
+
export function findTestFrameworkImport(content, virtualPath) {
|
|
261
|
+
const lang = langFromPath(virtualPath)
|
|
262
|
+
let result
|
|
263
|
+
try {
|
|
264
|
+
result = parseSync(virtualPath, content, { lang, sourceType: 'module' })
|
|
265
|
+
} catch {
|
|
266
|
+
return null
|
|
267
|
+
}
|
|
268
|
+
if (result.errors?.length) return null
|
|
269
|
+
for (const imp of result.module?.staticImports ?? []) {
|
|
270
|
+
const mod = imp.moduleRequest?.value
|
|
271
|
+
if (typeof mod === 'string' && TEST_FRAMEWORK_MODULES.has(mod)) return mod
|
|
272
|
+
}
|
|
273
|
+
/** @type {string | null} */
|
|
274
|
+
let found = null
|
|
275
|
+
walkAstWithAncestors(result.program, [], node => {
|
|
276
|
+
if (found) return
|
|
277
|
+
const reqMod = requireCallModule(node)
|
|
278
|
+
if (reqMod && TEST_FRAMEWORK_MODULES.has(reqMod)) {
|
|
279
|
+
found = reqMod
|
|
280
|
+
return
|
|
281
|
+
}
|
|
282
|
+
const dynMod = dynamicImportModule(node)
|
|
283
|
+
if (dynMod && TEST_FRAMEWORK_MODULES.has(dynMod)) {
|
|
284
|
+
found = dynMod
|
|
285
|
+
}
|
|
286
|
+
})
|
|
287
|
+
return found
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Класифікує опублікований файл як test/fixture, якщо хоча б одна з ознак:
|
|
292
|
+
* (1) у шляху є каталог із `TEST_DIR_NAMES`; (2) basename відповідає
|
|
293
|
+
* `TEST_FILE_PATTERNS`; (3) для JS/TS-розширень — імпорт test-фреймворку.
|
|
294
|
+
*
|
|
295
|
+
* Carve-out: для шляху `rules/<rule-name>/...` сегмент `<rule-name>` (індекс 1)
|
|
296
|
+
* — це ім'я правила, а не каталог. Зокрема, правило з id `test` (або `tests`)
|
|
297
|
+
* описує конвенцію розміщення тестів і саме по собі не є test-fixture'ом.
|
|
298
|
+
* Подальші сегменти (наприклад, `rules/<r>/js/<c>/tests/`) продовжують перевірятись.
|
|
299
|
+
* @param {string} relPath posix-шлях відносно `npm/`
|
|
300
|
+
* @returns {Promise<string | null>} причина порушення або `null`
|
|
301
|
+
* @param {string} [cwd] корінь репозиторію
|
|
302
|
+
*/
|
|
303
|
+
export async function classifyPublishedFileAsTest(relPath, cwd = process.cwd()) {
|
|
304
|
+
const segments = relPath.split('/')
|
|
305
|
+
const base = segments.at(-1)
|
|
306
|
+
const dirs = segments.slice(0, -1)
|
|
307
|
+
const testDir = dirs.find((seg, idx) => {
|
|
308
|
+
if (idx === 1 && dirs[0] === 'rules') {
|
|
309
|
+
return false
|
|
310
|
+
}
|
|
311
|
+
return TEST_DIR_NAMES.has(seg.toLowerCase())
|
|
312
|
+
})
|
|
313
|
+
if (testDir) return `test-style каталог "${testDir}/"`
|
|
314
|
+
if (TEST_FILE_PATTERNS.some(re => re.test(base))) return `test-style ім'я файлу`
|
|
315
|
+
if (JS_LIKE_EXT_RE.test(base)) {
|
|
316
|
+
const content = await readFile(join(cwd, 'npm', relPath), 'utf8')
|
|
317
|
+
const mod = findTestFrameworkImport(content, relPath)
|
|
318
|
+
if (mod) return `імпорт test-фреймворку "${mod}"`
|
|
319
|
+
}
|
|
320
|
+
return null
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Для всіх файлів, що потрапили б у tarball (positive `files` мінус negative
|
|
325
|
+
* patterns), забороняє test-style каталоги/імена/імпорти. Так пакет лишається
|
|
326
|
+
* компактним і не везе користувачам тести й фікстури.
|
|
327
|
+
* @param {(msg: string) => void} pass callback при успіху
|
|
328
|
+
* @param {(msg: string) => void} fail callback при порушенні
|
|
329
|
+
* @returns {Promise<void>}
|
|
330
|
+
* @param {string} cwd корінь репозиторію
|
|
331
|
+
*/
|
|
332
|
+
async function checkNoTestsInPublishedFiles(pass, fail, cwd) {
|
|
333
|
+
if (!existsSync(join(cwd, 'npm/package.json'))) return
|
|
334
|
+
const pkg = JSON.parse(await readFile(join(cwd, 'npm/package.json'), 'utf8'))
|
|
335
|
+
if (!Array.isArray(pkg.files)) return
|
|
336
|
+
const files = await collectPublishedFiles(pkg.files, cwd)
|
|
337
|
+
/** @type {{ file: string, reason: string }[]} */
|
|
338
|
+
const violations = []
|
|
339
|
+
for (const rel of files) {
|
|
340
|
+
const reason = await classifyPublishedFileAsTest(rel, cwd)
|
|
341
|
+
if (reason) violations.push({ file: rel, reason })
|
|
342
|
+
}
|
|
343
|
+
if (violations.length === 0) {
|
|
344
|
+
pass(`npm/: усі ${files.length} опублікованих файли без тестів і fixtures`)
|
|
345
|
+
return
|
|
346
|
+
}
|
|
347
|
+
for (const v of violations) {
|
|
348
|
+
fail(
|
|
349
|
+
`npm/${v.file}: ${v.reason} — додай у "files" у npm/package.json негативний glob, ` +
|
|
350
|
+
'що виключає цей файл з tarball (наприклад "!**/*.test.mjs", "!**/fixtures/**", "!**/*_test.rego") (npm-module.mdc)'
|
|
351
|
+
)
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Перевіряє базову структуру монорепо: наявність каталогу `npm/` і
|
|
357
|
+
* `npm/package.json`. Поле `workspaces ∋ "npm"` у кореневому `package.json`
|
|
358
|
+
* валідує `npm/policy/npm_module/root_package_json/`.
|
|
359
|
+
* @param {(msg: string) => void} pass callback при успішній перевірці
|
|
360
|
+
* @param {(msg: string) => void} fail callback при помилці
|
|
361
|
+
* @param {string} cwd корінь репозиторію
|
|
362
|
+
*/
|
|
363
|
+
async function checkNpmModuleBasicStructure(pass, fail, cwd) {
|
|
364
|
+
if (existsSync(join(cwd, 'package.json'))) {
|
|
365
|
+
pass('package.json існує')
|
|
366
|
+
} else {
|
|
367
|
+
fail('package.json не існує')
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const npmDir = join(cwd, 'npm')
|
|
371
|
+
if (existsSync(npmDir)) {
|
|
372
|
+
const s = await stat(npmDir)
|
|
373
|
+
if (s.isDirectory()) {
|
|
374
|
+
pass('npm/ директорія існує')
|
|
375
|
+
} else {
|
|
376
|
+
fail('npm має бути директорією')
|
|
377
|
+
}
|
|
378
|
+
} else {
|
|
379
|
+
fail('npm/ директорія не існує')
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (existsSync(join(cwd, 'npm/package.json'))) {
|
|
383
|
+
pass('npm/package.json існує')
|
|
384
|
+
} else {
|
|
385
|
+
fail('npm/package.json не існує — створи package.json для npm модуля')
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Перевіряє відповідність проєкту правилам npm-module.mdc
|
|
391
|
+
* @param {import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст лінту
|
|
392
|
+
* @returns {Promise<import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintResult>} результат лінту
|
|
393
|
+
*/
|
|
394
|
+
export async function lint(ctx) {
|
|
395
|
+
const cwd = ctx.cwd
|
|
396
|
+
const reporter = createViolationReporter(ctx)
|
|
397
|
+
const { pass, fail } = reporter
|
|
398
|
+
|
|
399
|
+
await checkNpmModuleBasicStructure(pass, fail, cwd)
|
|
400
|
+
await checkNoTestsInPublishedFiles(pass, fail, cwd)
|
|
401
|
+
|
|
402
|
+
const ignorePaths = await loadCursorIgnorePaths(cwd)
|
|
403
|
+
const useSrcJsLayout = await npmSrcTreeHasJsFile(cwd, ignorePaths)
|
|
404
|
+
|
|
405
|
+
await checkNpmPackageJson(useSrcJsLayout, pass, fail, cwd)
|
|
406
|
+
|
|
407
|
+
if (!useSrcJsLayout) {
|
|
408
|
+
await checkEmitTypesConfig(pass, fail, cwd)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const layoutLabel = useSrcJsLayout ? 'layout src' : 'tsconfig emit-types'
|
|
412
|
+
const hk = await readHkConfig(cwd)
|
|
413
|
+
if (hk) {
|
|
414
|
+
pass(`${hk.path} існує`)
|
|
415
|
+
const missing = useSrcJsLayout ? missingHkSrcLayoutFragments(hk.text) : missingHkEmitTypesConfigFragments(hk.text)
|
|
416
|
+
if (missing.length === 0) {
|
|
417
|
+
pass(`${hk.path}: pre-commit містить очікуваний виклик tsc (${layoutLabel})`)
|
|
418
|
+
} else {
|
|
419
|
+
fail(`${hk.path}: онови pre-commit крок (npm-module.mdc); не знайдено: ${missing.join(', ')}`)
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (DEPRECATED_CHECK_CHANGELOG_RE.test(hk.text)) {
|
|
423
|
+
fail(
|
|
424
|
+
`${hk.path}: крок містить застарілий виклик "check changelog" — команду \`check\` прибрано в v14 ` +
|
|
425
|
+
'(уніфікована поверхня `lint`). Заміни на "npx @7n/rules lint changelog" (npm-module.mdc)'
|
|
426
|
+
)
|
|
427
|
+
} else {
|
|
428
|
+
const missingChangelogStep = missingHkNpmChangelogFragments(hk.text)
|
|
429
|
+
if (missingChangelogStep.length === 0) {
|
|
430
|
+
pass(`${hk.path}: pre-commit містить крок npm-changelog (npm-module.mdc)`)
|
|
431
|
+
} else {
|
|
432
|
+
fail(`${hk.path}: онови крок npm-changelog (npm-module.mdc); не знайдено: ${missingChangelogStep.join(', ')}`)
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
} else {
|
|
436
|
+
fail('Очікується hk.pkl або .config/hk.pkl з pre-commit і tsc (npm-module.mdc)')
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (existsSync(join(cwd, '.github/workflows'))) {
|
|
440
|
+
pass('.github/workflows/ існує')
|
|
441
|
+
} else {
|
|
442
|
+
fail('.github/workflows/ не існує')
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
await checkPublishWorkflow(pass, fail, cwd)
|
|
446
|
+
|
|
447
|
+
return reporter.result()
|
|
448
|
+
}
|