@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,144 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Використання mssql в nodejs
|
|
3
|
+
globs: "**/package.json,**/src/conn/mssql-*"
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
version: '1.4'
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Правило охоплює безпечне використання пакету `mssql` у Node.js: мінімальну версію залежності, singleton ConnectionPool, заборону небезпечних шаблонів запитів і захист від SQL injection через TVP або числовий парсинг IN-списків.
|
|
9
|
+
|
|
10
|
+
## Швидкий gate через conftest
|
|
11
|
+
|
|
12
|
+
Rego-перевірки (`policy/`) — швидкий синхронний gate для одиничного `package.json` без JS-рантайму:
|
|
13
|
+
|
|
14
|
+
JS-перевірка (`deps.mjs`) — authoritative: охоплює всі `package.json` репо, AST-скан JS/TS-джерел і повний semver-triple-compare. Rego-gate — доповнення, не заміна.
|
|
15
|
+
|
|
16
|
+
## Singleton ConnectionPool і заборона shared Request
|
|
17
|
+
|
|
18
|
+
Потрібно використовувати connection pool (`sql.ConnectionPool`) як singleton, а **НЕ** створювати підключення на кожен запит. Перевіряється `findMssqlPerRequestConnectionInText` (`lib/mssql-pool-scan.mjs`, підключено в `deps/main.mjs`).
|
|
19
|
+
|
|
20
|
+
```javascript
|
|
21
|
+
// db.js
|
|
22
|
+
import sql from 'mssql';
|
|
23
|
+
|
|
24
|
+
let poolPromise;
|
|
25
|
+
|
|
26
|
+
export function getPool() {
|
|
27
|
+
if (!poolPromise) {
|
|
28
|
+
const db = new sql.ConnectionPool(config);
|
|
29
|
+
poolPromise = db.connect().catch(err => {
|
|
30
|
+
poolPromise = undefined; // дозволити повторну спробу
|
|
31
|
+
throw err;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return poolPromise;
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```javascript
|
|
39
|
+
// usage
|
|
40
|
+
import { getPool } from './db.js';
|
|
41
|
+
|
|
42
|
+
const pool = await getPool();
|
|
43
|
+
const userId = 42;
|
|
44
|
+
const status = 'active';
|
|
45
|
+
|
|
46
|
+
// Tagged template на request — це працює
|
|
47
|
+
const result = await pool.request().query`
|
|
48
|
+
SELECT * FROM users
|
|
49
|
+
WHERE id = ${userId} AND status = ${status}
|
|
50
|
+
`;
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Ключове: `pool.request().query\`...\`` — бектіки після `query`, без круглих дужок. Це tagged template, де mssql параметризує значення автоматично.
|
|
54
|
+
|
|
55
|
+
### Заборона shared Request
|
|
56
|
+
|
|
57
|
+
Заборонено робити singleton `request` на рівні модуля (перевіряється `findSharedMssqlRequestInText`):
|
|
58
|
+
|
|
59
|
+
```javascript
|
|
60
|
+
// ❌ НЕ МОЖНА
|
|
61
|
+
export const request = pool.request();
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`Request` має створюватися **щоразу заново**:
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
// ✅ МОЖНА
|
|
68
|
+
const request = pool.request();
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Це особливо важливо для **TVP** (табличні параметри, див. нижче) та будь-яких `.input(...)`.
|
|
72
|
+
|
|
73
|
+
## Безпечний tagged template vs небезпечний `query(...)`
|
|
74
|
+
|
|
75
|
+
Різниця між `query\`...\`` і `query(\`...\`)` — критична. Перевіряється `findUnsafeMssqlQueryTemplateCallInText`.
|
|
76
|
+
|
|
77
|
+
### Що НЕ робити: `query(\`...\`)`
|
|
78
|
+
|
|
79
|
+
```javascript
|
|
80
|
+
// ❌ Це не tagged template — це конкатенація рядка перед викликом
|
|
81
|
+
await pool.request().query(`SELECT * FROM users WHERE id = ${userId}`);
|
|
82
|
+
// ↑ круглі дужки замість бектіків = звичайна інтерполяція = SQL injection
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Що робити: `query\`...\``
|
|
86
|
+
|
|
87
|
+
```javascript
|
|
88
|
+
// ✅ Tagged template — mssql параметризує ${userId} автоматично
|
|
89
|
+
await pool.request().query`SELECT * FROM users WHERE id = ${userId}`;
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Коментар під час виправлення SQL injection
|
|
93
|
+
|
|
94
|
+
Коли виправляєш місце з потенційним SQL injection (заміна `query(\`...\`)` на `query\`...\`` або прибирання динамічних списків/конкатенації), **додай поруч короткий коментар** з описом причини.
|
|
95
|
+
|
|
96
|
+
Вимоги до коментаря:
|
|
97
|
+
|
|
98
|
+
- вказати **що було небезпечно** (звичайна інтерполяція в рядок, конкатенація, динамічний список);
|
|
99
|
+
- вказати **чому новий варіант безпечний** (tagged template / параметризація / TVP);
|
|
100
|
+
- 1–2 рядки, без дублювання очевидного.
|
|
101
|
+
|
|
102
|
+
```javascript
|
|
103
|
+
// SQLi fix: query`...` (tagged template) параметризує значення; query(`...`) небезпечний через інтерполяцію.
|
|
104
|
+
await pool.request().query`SELECT * FROM users WHERE id = ${userId}`
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Парсинг і guard для `IN (${...})`
|
|
108
|
+
|
|
109
|
+
Якщо `IN (...)` все ж використовується (а не `JOIN` на TVP), значення в `${...}` **обов'язково** мають бути попередньо приведені числовим парсером і відфільтровані від `NaN`. Перевіряється `findUnsafeMssqlInListUnparsedInText` (наявність парсера) і `findUnsafeMssqlInListMissingEmptyGuardInText` (винесення у змінну + guard на пустоту); динамічні списки через `.join(...)` додатково ловить `findUnsafeMssqlDynamicSqlListInText`.
|
|
110
|
+
|
|
111
|
+
Це знімає будь-яку можливість SQL injection: SQL-метасимволи в `Number`/`parseInt(...)` перетворюються на `NaN` і відсіюються.
|
|
112
|
+
|
|
113
|
+
### Правила
|
|
114
|
+
|
|
115
|
+
Додатково:
|
|
116
|
+
|
|
117
|
+
- значення для `IN (${...})` потрібно **винести в окрему змінну** перед запитом (не підставляти вираз напряму в `${...}`);
|
|
118
|
+
- цю змінну потрібно **перевірити на пустоту** і якщо список порожній — **throw error** (щоб не виконувати некоректний запит).
|
|
119
|
+
|
|
120
|
+
### Приклади
|
|
121
|
+
|
|
122
|
+
```javascript
|
|
123
|
+
// ❌ НЕ МОЖНА: значення з req.body / зовнішнього джерела без парсингу
|
|
124
|
+
const outIds = pgQ.rows.flatMap(x => x.req_body.Orders.map(o => o.OutletId))
|
|
125
|
+
await pool.query(/* sql */ String.raw`
|
|
126
|
+
SELECT ... WHERE so.OutletId IN (${outIds})
|
|
127
|
+
`)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
```javascript
|
|
131
|
+
// ✅ МОЖНА: parseInt + filter(!isNaN) гарантує, що в SQL потраплять лише числа
|
|
132
|
+
// і перед запитом робимо guard на пустоту, щоб не виконувати некоректний SQL.
|
|
133
|
+
const outIds = pgQ.rows
|
|
134
|
+
.flatMap(x => x.req_body.Orders.map(o => parseInt(o.OutletId)))
|
|
135
|
+
.filter(n => !isNaN(n))
|
|
136
|
+
if (!outIds.length) throw new Error('outIds is empty')
|
|
137
|
+
await pool.request().query`
|
|
138
|
+
SELECT ... WHERE so.OutletId IN (${outIds})
|
|
139
|
+
`
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Допустимі парсери: `parseInt(...)`, `parseFloat(...)`, `Number(...)`, `BigInt(...)` або унарний `+x`. Літеральні масиви чисел (`[1, 2, 3]`) теж безпечні — без парсера, але без жодних рядків.
|
|
143
|
+
|
|
144
|
+
Це правило діє і для безпечного `pool.request().query\`...\`` (де mssql сам параметризує масив), і поготів для `pool.query(String.raw\`...\`)` чи `pool.query(\`...\`)`, де такий парсинг — єдиний бар'єр.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
## TVP (table-valued parameters) — рекомендований підхід для списків
|
|
2
|
+
|
|
3
|
+
TVP дозволений і рекомендований для SQL Server 2019. Для списків значень та пар ключів **найкращий** шлях по безпеці/продуктивності — TVP.
|
|
4
|
+
|
|
5
|
+
Позитивна практика без прямого AST-check: антипатерн (динамічний список через `.join(...)` у `IN (...)`/`VALUES (...)`) ловить `findUnsafeMssqlDynamicSqlListInText` (`lib/mssql-pool-scan.mjs`, див. `main.mdc`), а сам "правильний" спосіб побудови TVP код не сканує.
|
|
6
|
+
|
|
7
|
+
### 1) `IN (...)` для кодів → `JOIN` на TVP
|
|
8
|
+
|
|
9
|
+
Замість складання SQL-рядка:
|
|
10
|
+
|
|
11
|
+
```javascript
|
|
12
|
+
// ❌ НЕ МОЖНА: динамічний список у SQL
|
|
13
|
+
await pool.request().query`
|
|
14
|
+
SELECT *
|
|
15
|
+
FROM promo.SomeTable t
|
|
16
|
+
WHERE t.ExternalCode IN (${codes.map(c => `'${c}'`).join(',')})
|
|
17
|
+
`;
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Роби TVP з 1 колонкою:
|
|
21
|
+
|
|
22
|
+
- створити `new sql.Table()`
|
|
23
|
+
- `columns.add('ExternalCode', sql.NVarChar(N))`
|
|
24
|
+
- `rows.add(code)` після `trim()` + валідації
|
|
25
|
+
- `request.input('codes', table)`
|
|
26
|
+
- в SQL: `JOIN @codes c ON c.ExternalCode = t.ExternalCode`
|
|
27
|
+
|
|
28
|
+
Плюси:
|
|
29
|
+
|
|
30
|
+
- 0% SQL injection
|
|
31
|
+
- текст SQL **не росте** від довжини масиву
|
|
32
|
+
- SQL Server часто оптимізує `JOIN` краще, ніж гігантський `IN (...)`
|
|
33
|
+
|
|
34
|
+
### 2) `DELETE ... VALUES (...)` / `MERGE ... VALUES (...)` → TVP з 2 колонками
|
|
35
|
+
|
|
36
|
+
Замість генерації списку рядків `(...),(...),...` у SQL (навіть якщо воно "через tagged template"):
|
|
37
|
+
|
|
38
|
+
- сформуй TVP-таблицю `@Pairs` з колонками:
|
|
39
|
+
- `PromoActivitiesId` (INT або BIGINT — як у схемі БД)
|
|
40
|
+
- `SupplierOutletId` (INT або BIGINT — як у схемі БД)
|
|
41
|
+
|
|
42
|
+
Delete:
|
|
43
|
+
|
|
44
|
+
```sql
|
|
45
|
+
DELETE abi
|
|
46
|
+
FROM promo.ActivitiesByOutlet abi
|
|
47
|
+
JOIN @Pairs p
|
|
48
|
+
ON p.PromoActivitiesId = abi.PromoActivitiesId
|
|
49
|
+
AND p.SupplierOutletId = abi.SupplierOutletId;
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Insert (без `MERGE`, простіше і безпечніше):
|
|
53
|
+
|
|
54
|
+
```sql
|
|
55
|
+
INSERT INTO promo.ActivitiesByOutlet (PromoActivitiesId, SupplierOutletId, Status)
|
|
56
|
+
SELECT p.PromoActivitiesId, p.SupplierOutletId, 2
|
|
57
|
+
FROM @Pairs p
|
|
58
|
+
WHERE NOT EXISTS (
|
|
59
|
+
SELECT 1
|
|
60
|
+
FROM promo.ActivitiesByOutlet abi
|
|
61
|
+
WHERE abi.PromoActivitiesId = p.PromoActivitiesId
|
|
62
|
+
AND abi.SupplierOutletId = p.SupplierOutletId
|
|
63
|
+
);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Плюси:
|
|
67
|
+
|
|
68
|
+
- не збираєш SQL-рядок із даних
|
|
69
|
+
- менше шансів упіймати edge-case `MERGE` (у SQL Server історично багато "сюрпризів")
|
|
70
|
+
|
|
71
|
+
### Мінімальна валідація перед наповненням TVP
|
|
72
|
+
|
|
73
|
+
Навіть з TVP потрібно:
|
|
74
|
+
|
|
75
|
+
- `ExternalCode`: `typeof === 'string'`, `trim()`, довжина `<= N` (під схему), відкинути пусті.
|
|
76
|
+
- ліміт на кількість елементів (наприклад 5k/10k — залежить від вашого потоку).
|
|
77
|
+
- `supplierId`/ID: число/BigInt, валідне та скінченне.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
## Rego-gate: версія `mssql` у `package.json`
|
|
2
|
+
|
|
3
|
+
Rego-пакет: `js_mssql.package_json`
|
|
4
|
+
|
|
5
|
+
Цільові файли: `**/package.json`
|
|
6
|
+
|
|
7
|
+
Перевіряє поле `dependencies.mssql` — якщо присутнє, версія має бути `>= 12.5.0`. Підтримувані формати: `^12.5.0`, `>=12.5.0`, `12.5.0`, `workspace:*` (OK). Версії нижче 12.5.x генерують `deny`.
|
|
8
|
+
|
|
9
|
+
Це швидкий синхронний gate для одиничного `package.json`. Authoritative-перевірка (повний semver-triple-compare, усі `package.json` репо) — у JS (`fix.mjs`).
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Порт перевірки версії `mssql` з `npm/scripts/rules/js-mssql/fix.mjs` (js-mssql.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Запуск (локально, для будь-якого `package.json`):
|
|
4
|
+
# conftest test path/to/package.json -p npm/policy/js_mssql \
|
|
5
|
+
# --namespace js_mssql.package_json
|
|
6
|
+
#
|
|
7
|
+
# Перевіряє: якщо `dependencies.mssql` присутній, версія має бути >= 12.5.0.
|
|
8
|
+
# Підтримує `^12.5.0`, `>=12.5.0`, `12.5.0`, `workspace:*` (трактується як OK).
|
|
9
|
+
#
|
|
10
|
+
# AST-скан коду на per-request `new sql.ConnectionPool(...)` всередині функцій
|
|
11
|
+
# (потребує парсингу `.js` / `.ts` через oxc-parser), а також full-semver
|
|
12
|
+
# (`major.minor.patch` triple-compare у `rules/js-mssql/fix.mjs`) лишаються у JS:
|
|
13
|
+
# JS-перевірка authoritative, ця Rego — швидкий gate для одиничного `package.json`.
|
|
14
|
+
#
|
|
15
|
+
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
16
|
+
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
|
|
17
|
+
# (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
|
|
18
|
+
package js_mssql.package_json
|
|
19
|
+
|
|
20
|
+
import rego.v1
|
|
21
|
+
|
|
22
|
+
deny contains msg if {
|
|
23
|
+
range := object.get(object.get(input, "dependencies", {}), "mssql", "")
|
|
24
|
+
range != ""
|
|
25
|
+
not mssql_version_meets_min(range)
|
|
26
|
+
msg := sprintf("dependencies.mssql має бути >= 12.5.0 (зараз %q) (js-mssql.mdc)", [range])
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
# Мінімум — 12.5.0 (js-mssql.mdc). `workspace:*` трактуємо як OK (узгоджено з JS).
|
|
30
|
+
mssql_version_meets_min(range) if startswith(trim_space(range), "workspace:")
|
|
31
|
+
|
|
32
|
+
mssql_version_meets_min(range) if {
|
|
33
|
+
parts := split_to_numbers(range)
|
|
34
|
+
count(parts) >= 3
|
|
35
|
+
parts[0] > 12
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
mssql_version_meets_min(range) if {
|
|
39
|
+
parts := split_to_numbers(range)
|
|
40
|
+
count(parts) >= 3
|
|
41
|
+
parts[0] == 12
|
|
42
|
+
parts[1] > 5
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
mssql_version_meets_min(range) if {
|
|
46
|
+
parts := split_to_numbers(range)
|
|
47
|
+
count(parts) >= 3
|
|
48
|
+
parts[0] == 12
|
|
49
|
+
parts[1] == 5
|
|
50
|
+
parts[2] >= 0
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
split_to_numbers(spec) := nums if {
|
|
54
|
+
tokens := regex.split(`\D+`, spec)
|
|
55
|
+
non_empty := [t | some t in tokens; t != ""]
|
|
56
|
+
nums := [n | some t in non_empty; n := to_number(t)]
|
|
57
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
## Rego-gate: OTEL ConfigMap (k8s)
|
|
2
|
+
|
|
3
|
+
Rego-пакет: `js-run.configmap`
|
|
4
|
+
|
|
5
|
+
**Цільові файли:** `k8s/base/configmap.yaml` (і будь-які ConfigMap у kustomize-оверлеях)
|
|
6
|
+
|
|
7
|
+
**Умова спрацювання:** `input.kind == "ConfigMap"`
|
|
8
|
+
|
|
9
|
+
Перевіряє, що поле `data.OTEL_RESOURCE_ATTRIBUTES` містить усі обовʼязкові substring-маркери, визначені у template:
|
|
10
|
+
|
|
11
|
+
- `service.name=`
|
|
12
|
+
- `service.namespace=`
|
|
13
|
+
|
|
14
|
+
Канон маркерів: [configmap.yaml.contains.yml](./template/configmap.yaml.contains.yml)
|
|
15
|
+
|
|
16
|
+
**✓ Правильно**
|
|
17
|
+
|
|
18
|
+
```yaml
|
|
19
|
+
data:
|
|
20
|
+
OTEL_RESOURCE_ATTRIBUTES: 'service.name=my-svc,service.namespace=prod'
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**✗ Неправильно**
|
|
24
|
+
|
|
25
|
+
```yaml
|
|
26
|
+
data:
|
|
27
|
+
OTEL_RESOURCE_ATTRIBUTES: 'service.name=my-svc'
|
|
28
|
+
# Відсутній service.namespace= → deny
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Ресурси типу `kind: Deployment` та інші non-ConfigMap — ігноруються.
|
|
32
|
+
|
|
33
|
+
### Kustomize-оверлеї: `service.namespace` має відповідати namespace оверлею (не автоматизовано)
|
|
34
|
+
|
|
35
|
+
Конвенція, **не** покрита автоматичною перевіркою — звіряй вручну під час рев'ю: у директоріях з kustomize-оверлеями значення `OTEL_RESOURCE_ATTRIBUTES` повинні бути перевизначені, і в них `service.namespace` повинен відповідати namespace, у якому знаходиться дана директорія оверлею (наприклад, `k8s/overlays/staging/` → `service.namespace=staging`).
|
|
36
|
+
|
|
37
|
+
Rego-gate вище цього не звіряє — він лише перевіряє наявність обох substring-маркерів у кожному ConfigMap незалежно від директорії, тому розбіжність `service.namespace` між `base` і kustomize-оверлеями пройде перевірку мовчки. Кросс-файлова резолюція kustomize-дерева (`base` + `overlays`) — складніша задача, вже реалізована для інших k8s-перевірок у `npm/rules/k8s/manifests/main.mjs`; переносити цю логіку в `js-run` окремо не варто, поки немає підтвердженого запиту на автоматизацію саме цього кейсу.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Перевірка ConfigMap (js-run.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Канон надходить через --data: { "template": { "contains": ... } }
|
|
4
|
+
# Структура --data сформована з template/configmap.yaml.contains.yml.
|
|
5
|
+
# Контекст: kind=ConfigMap; OTEL_RESOURCE_ATTRIBUTES має містити кожен substring
|
|
6
|
+
# зі snippet (`service.name=`, `service.namespace=`).
|
|
7
|
+
package js_run.configmap
|
|
8
|
+
|
|
9
|
+
import rego.v1
|
|
10
|
+
|
|
11
|
+
deny contains msg if {
|
|
12
|
+
input.kind == "ConfigMap"
|
|
13
|
+
some field, needles in data.template.contains.data
|
|
14
|
+
actual := object.get(object.get(input, "data", {}), field, "")
|
|
15
|
+
actual != ""
|
|
16
|
+
some needle in needles
|
|
17
|
+
not contains(actual, needle)
|
|
18
|
+
msg := sprintf("ConfigMap %q: %s має містити %q (js-run.mdc)", [cm_name, field, needle])
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
cm_name := object.get(object.get(input, "metadata", {}), "name", "?")
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: fix-jsconfig.mjs
|
|
4
|
+
resource: plugins/lang-js/rules/js-run/jsconfig/fix-jsconfig.mjs
|
|
5
|
+
docgen:
|
|
6
|
+
crc: 47315a24
|
|
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
|
+
Цей файл виконує merge-запис `jsconfig.json` з канонічними значеннями з `template/jsconfig.json.snippet.json` (`data.template.snippet`), щоб політика `js-run/jsconfig` залишалася однаковою всюди, де вона застосовується. Leaf-рівень `compilerOptions` перезаписується точними канонічними значеннями, бо rego очікує exact match, а top-level масиви на кшталт `include` теж звіряються як exact set. Поза каноном зберігаються локальні поля, інші секції `compilerOptions` і супровідні коментарі. Обробка fail-safe: помилки перехоплюються, назовні не кидаються, а за окремих помилок повертається порожнє значення.
|
|
16
|
+
|
|
17
|
+
## Поведінка
|
|
18
|
+
|
|
19
|
+
1. Знаходить випадки, де `jsconfig.json` порушує політику узгодження з канонічним `jsconfig.json.snippet.json`.
|
|
20
|
+
2. Для кожного такого файлу звіряє значення з каноном і приводить до точного збігу ті поля, які визначені у сніпеті: `compilerOptions` оновлює на рівні окремих вкладених значень, а top-level масиви — як цілісні набори.
|
|
21
|
+
3. Зберігає локальні нестандартні дані файлу поза канонічними полями, щоб не втратити супровідні секції чи коментарі-джерела.
|
|
22
|
+
4. Якщо канонічний сніпет недоступний або файл не читається як JSON, не падає і просто нічого не змінює.
|
|
23
|
+
5. Після успішного виправлення записує оновлений `jsconfig.json` і повертає перелік змінених файлів та короткий опис змін.
|
|
24
|
+
|
|
25
|
+
## Гарантії поведінки
|
|
26
|
+
|
|
27
|
+
- Перехоплює помилки і не пропускає винятків назовні (fail-safe).
|
|
28
|
+
- За певних помилок повертає порожнє значення (напр. `null`) замість винятку.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T0-autofix для policy-concern-а `js-run/jsconfig`: merge-запис `jsconfig.json` з
|
|
3
|
+
* канонічними значеннями (`template/jsconfig.json.snippet.json`, той самий `data.template.snippet`,
|
|
4
|
+
* що бачить rego). Leaf-рівень `compilerOptions` перезаписується точним канонічним значенням
|
|
5
|
+
* (rego вимагає exact match, не subset), top-level масиви (`include`) — теж exact set.
|
|
6
|
+
* Локальні поля поза каноном (інші секції compilerOptions, коментарі-джерела) — зберігаються.
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
9
|
+
import { join } from 'node:path'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Читає JSON-файл template-сніпета концерну; відсутній/невалідний → null.
|
|
13
|
+
* @param {string} path абсолютний шлях
|
|
14
|
+
* @returns {object|null} розпарсений об'єкт або null
|
|
15
|
+
*/
|
|
16
|
+
function readSnippet(path) {
|
|
17
|
+
if (!existsSync(path)) return null
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(readFileSync(path, 'utf8'))
|
|
20
|
+
} catch {
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {unknown} a значення
|
|
27
|
+
* @param {unknown} b значення
|
|
28
|
+
* @returns {boolean} чи однакові як множини (масиви) або строго (інше)
|
|
29
|
+
*/
|
|
30
|
+
function valuesMatch(a, b) {
|
|
31
|
+
if (Array.isArray(b)) {
|
|
32
|
+
return Array.isArray(a) && new Set(a).size === new Set(b).size && b.every(x => a.includes(x))
|
|
33
|
+
}
|
|
34
|
+
return a === b
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Мерджить канонічну секцію (`compilerOptions`) у `cfg[field]`: кожен leaf перезаписується
|
|
39
|
+
* канонічним значенням, якщо відрізняється.
|
|
40
|
+
* @param {object} cfg поточний jsconfig
|
|
41
|
+
* @param {string} field назва секції
|
|
42
|
+
* @param {object} expectedSection канонічні leaf-значення секції
|
|
43
|
+
* @returns {string[]} список змінених шляхів (`field.leaf`)
|
|
44
|
+
*/
|
|
45
|
+
function mergeSection(cfg, field, expectedSection) {
|
|
46
|
+
const changes = []
|
|
47
|
+
const inner = typeof cfg[field] === 'object' && cfg[field] !== null ? cfg[field] : {}
|
|
48
|
+
cfg[field] = inner
|
|
49
|
+
for (const [leaf, leafExpected] of Object.entries(expectedSection)) {
|
|
50
|
+
if (valuesMatch(inner[leaf], leafExpected)) continue
|
|
51
|
+
inner[leaf] = leafExpected
|
|
52
|
+
changes.push(`${field}.${leaf}`)
|
|
53
|
+
}
|
|
54
|
+
return changes
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Мерджить канонічний snippet у `jsconfig.json`: leaf-рівень (`compilerOptions.*`) і
|
|
59
|
+
* top-level масиви перезаписуються канонічним значенням, якщо відрізняються.
|
|
60
|
+
* @param {object} cfg поточний jsconfig
|
|
61
|
+
* @param {object} snippet канонічний snippet
|
|
62
|
+
* @returns {string[]} список змінених шляхів (для message)
|
|
63
|
+
*/
|
|
64
|
+
function mergeSnippet(cfg, snippet) {
|
|
65
|
+
const changes = []
|
|
66
|
+
for (const [field, expected] of Object.entries(snippet)) {
|
|
67
|
+
if (Array.isArray(expected)) {
|
|
68
|
+
if (!valuesMatch(cfg[field], expected)) {
|
|
69
|
+
cfg[field] = expected
|
|
70
|
+
changes.push(field)
|
|
71
|
+
}
|
|
72
|
+
continue
|
|
73
|
+
}
|
|
74
|
+
if (typeof expected === 'object' && expected !== null) {
|
|
75
|
+
changes.push(...mergeSection(cfg, field, expected))
|
|
76
|
+
continue
|
|
77
|
+
}
|
|
78
|
+
if (cfg[field] !== expected) {
|
|
79
|
+
cfg[field] = expected
|
|
80
|
+
changes.push(field)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return changes
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** @type {import('@7n/rules/scripts/lib/lint-surface/types.mjs').T0Pattern[]} */
|
|
87
|
+
export const patterns = [
|
|
88
|
+
{
|
|
89
|
+
id: 'jsconfig-merge-canon',
|
|
90
|
+
test: violations => violations.some(v => v.reason === 'policy-deny' && v.file),
|
|
91
|
+
apply: (violations, ctx) => {
|
|
92
|
+
const snippet = readSnippet(join(ctx.concernDir ?? '', 'template', 'jsconfig.json.snippet.json'))
|
|
93
|
+
if (!snippet) return { touchedFiles: [] }
|
|
94
|
+
|
|
95
|
+
const files = [...new Set(violations.filter(v => v.file).map(v => v.file))]
|
|
96
|
+
const touchedFiles = []
|
|
97
|
+
const messages = []
|
|
98
|
+
|
|
99
|
+
for (const rel of files) {
|
|
100
|
+
const abs = join(ctx.cwd, rel)
|
|
101
|
+
let cfg
|
|
102
|
+
try {
|
|
103
|
+
cfg = JSON.parse(readFileSync(abs, 'utf8'))
|
|
104
|
+
} catch {
|
|
105
|
+
continue
|
|
106
|
+
}
|
|
107
|
+
const changes = mergeSnippet(cfg, snippet)
|
|
108
|
+
if (changes.length === 0) continue
|
|
109
|
+
|
|
110
|
+
ctx.recordWrite?.(abs)
|
|
111
|
+
writeFileSync(abs, `${JSON.stringify(cfg, null, 2)}\n`)
|
|
112
|
+
touchedFiles.push(abs)
|
|
113
|
+
messages.push(`${rel}: ${changes.join(', ')}`)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return touchedFiles.length > 0 ? { touchedFiles, message: messages.join('; ') } : { touchedFiles: [] }
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
## `jsconfig.json` (редактор / перевірка типів)
|
|
2
|
+
|
|
3
|
+
Якщо в **backend** workspace-пакеті (без `vite` у `devDependencies`) є каталог **`src/`**, у **корені цього пакета** має бути **`jsconfig.json`**. Якщо файлу ще немає — створи його з таким вмістом (канон js-run):
|
|
4
|
+
|
|
5
|
+
```json title="jsconfig.json"
|
|
6
|
+
{
|
|
7
|
+
"compilerOptions": {
|
|
8
|
+
"lib": ["esnext"],
|
|
9
|
+
"module": "NodeNext",
|
|
10
|
+
"moduleResolution": "NodeNext",
|
|
11
|
+
"target": "esnext",
|
|
12
|
+
"checkJs": false
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*"]
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Канон і повний перелік перевірених полів — див. розділ нижче **Rego-gate: jsconfig.json**, канонічний файл: [jsconfig.json.snippet.json](./template/jsconfig.json.snippet.json).
|
|
19
|
+
|
|
20
|
+
Якщо пакет не слідує структурі з `src/` (наприклад, лише `scripts/` у корені) — ця вимога не застосовується; для типових сервісів із `src/` файл обов'язковий і має збігатися з каноном.
|
|
21
|
+
|
|
22
|
+
Fs-existence створення `jsconfig.json`, коли його бракує, — T0-автофікс (`../runtime/fix-runtime.mjs`); структурну звірку канону виконує rego-gate нижче.
|
|
23
|
+
|
|
24
|
+
## Rego-gate: jsconfig.json
|
|
25
|
+
|
|
26
|
+
Rego-пакет: `js-run.jsconfig`
|
|
27
|
+
|
|
28
|
+
**Цільові файли:** `jsconfig.json` у корені backend workspace-пакету
|
|
29
|
+
|
|
30
|
+
Порівнює `jsconfig.json` з каноном через walker по всіх листах template:
|
|
31
|
+
|
|
32
|
+
- `compilerOptions.*` — значення мають збігатись точно
|
|
33
|
+
- `include` — масив порівнюється як множина (точний склад)
|
|
34
|
+
|
|
35
|
+
Канон: [jsconfig.json.snippet.json](./template/jsconfig.json.snippet.json)
|
|
36
|
+
|
|
37
|
+
Перевірені поля:
|
|
38
|
+
|
|
39
|
+
| Поле | Очікуване значення |
|
|
40
|
+
|------|--------------------|
|
|
41
|
+
| `compilerOptions.module` | `"NodeNext"` |
|
|
42
|
+
| `compilerOptions.moduleResolution` | `"NodeNext"` |
|
|
43
|
+
| `compilerOptions.target` | `"esnext"` |
|
|
44
|
+
| `compilerOptions.lib` | `["esnext"]` |
|
|
45
|
+
| `compilerOptions.checkJs` | `false` |
|
|
46
|
+
| `include` | `["src/**/*"]` |
|
|
47
|
+
|
|
48
|
+
Якщо розділ `compilerOptions` відсутній або не є обʼєктом — deny.
|