@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.
Files changed (258) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/package.json +14 -4
  3. package/rules/bun/bunfig/bunfig.mdc +17 -0
  4. package/rules/bun/bunfig/bunfig.rego +29 -0
  5. package/rules/bun/bunfig/concern.json +9 -0
  6. package/rules/bun/bunfig/template/bunfig.toml.snippet.toml +2 -0
  7. package/rules/bun/docs/index.md +11 -0
  8. package/rules/bun/layout/concern.json +16 -0
  9. package/rules/bun/layout/docs/fix-layout.md +29 -0
  10. package/rules/bun/layout/docs/main.md +34 -0
  11. package/rules/bun/layout/fix-layout.mjs +63 -0
  12. package/rules/bun/layout/layout.mdc +60 -0
  13. package/rules/bun/layout/main.mjs +53 -0
  14. package/rules/bun/licensee/concern.json +7 -0
  15. package/rules/bun/licensee/docs/fix-licensee.md +27 -0
  16. package/rules/bun/licensee/docs/index.md +12 -0
  17. package/rules/bun/licensee/docs/main.md +30 -0
  18. package/rules/bun/licensee/fix-licensee.mjs +31 -0
  19. package/rules/bun/licensee/main.mjs +68 -0
  20. package/rules/bun/lint-surface/concern.json +3 -0
  21. package/rules/bun/lint-surface/lint-surface.mdc +13 -0
  22. package/rules/bun/main.json +1 -0
  23. package/rules/bun/main.mdc +11 -0
  24. package/rules/bun/package_json/concern.json +9 -0
  25. package/rules/bun/package_json/docs/fix-package_json.md +28 -0
  26. package/rules/bun/package_json/docs/index.md +9 -0
  27. package/rules/bun/package_json/fix-package_json.mjs +311 -0
  28. package/rules/bun/package_json/package_json.mdc +14 -0
  29. package/rules/bun/package_json/package_json.rego +64 -0
  30. package/rules/bun/package_json/template/package.json.deny.json +4 -0
  31. package/rules/js/check/check.mdc +26 -0
  32. package/rules/js/check/concern.json +19 -0
  33. package/rules/js/check/docs/eslint-config.md +56 -0
  34. package/rules/js/check/docs/fix-check.md +48 -0
  35. package/rules/js/check/docs/index.md +13 -0
  36. package/rules/js/check/docs/main.md +48 -0
  37. package/rules/js/check/eslint-config.mjs +262 -0
  38. package/rules/js/check/fix-check.mjs +82 -0
  39. package/rules/js/check/main.mjs +310 -0
  40. package/rules/js/dep-policy/concern.json +4 -0
  41. package/rules/js/dep-policy/dep-policy.mdc +36 -0
  42. package/rules/js/dep-policy/docs/main.md +35 -0
  43. package/rules/js/dep-policy/main.mjs +99 -0
  44. package/rules/js/docs/index.md +11 -0
  45. package/rules/js/eslint/concern.json +8 -0
  46. package/rules/js/eslint/docs/fix-eslint.md +50 -0
  47. package/rules/js/eslint/docs/fix-worker.md +31 -0
  48. package/rules/js/eslint/docs/index.md +11 -0
  49. package/rules/js/eslint/docs/main.md +35 -0
  50. package/rules/js/eslint/fix-eslint.mjs +160 -0
  51. package/rules/js/eslint/fix-worker.mjs +139 -0
  52. package/rules/js/eslint/main.mjs +115 -0
  53. package/rules/js/file-extensions/concern.json +3 -0
  54. package/rules/js/file-extensions/file-extensions.mdc +12 -0
  55. package/rules/js/jscpd_config/concern.json +11 -0
  56. package/rules/js/jscpd_config/docs/fix-jscpd_config.md +25 -0
  57. package/rules/js/jscpd_config/docs/index.md +9 -0
  58. package/rules/js/jscpd_config/fix-jscpd_config.mjs +3 -0
  59. package/rules/js/jscpd_config/jscpd_config.mdc +42 -0
  60. package/rules/js/jscpd_config/jscpd_config.rego +44 -0
  61. package/rules/js/jscpd_config/template/.jscpd.json.snippet.json +7 -0
  62. package/rules/js/jscpd_duplicates/concern.json +7 -0
  63. package/rules/js/jscpd_duplicates/docs/main.md +29 -0
  64. package/rules/js/jscpd_duplicates/main.mjs +69 -0
  65. package/rules/js/knip/concern.json +7 -0
  66. package/rules/js/knip/docs/main.md +31 -0
  67. package/rules/js/knip/knip.mdc +15 -0
  68. package/rules/js/knip/main.mjs +89 -0
  69. package/rules/js/lint-findings/concern.json +3 -0
  70. package/rules/js/lint-findings/docs/main.md +40 -0
  71. package/rules/js/lint-findings/main.mjs +125 -0
  72. package/rules/js/main.json +1 -0
  73. package/rules/js/main.mdc +18 -0
  74. package/rules/js/package_json/concern.json +9 -0
  75. package/rules/js/package_json/docs/fix-package_json.md +25 -0
  76. package/rules/js/package_json/docs/index.md +9 -0
  77. package/rules/js/package_json/fix-package_json.mjs +3 -0
  78. package/rules/js/package_json/package_json.mdc +15 -0
  79. package/rules/js/package_json/package_json.rego +142 -0
  80. package/rules/js/package_json/template/package.json.snippet.json +6 -0
  81. package/rules/js/tooling/concern.json +3 -0
  82. package/rules/js/tooling/data/tooling/knip-canonical.json +30 -0
  83. package/rules/js/tooling/data/tooling/oxlint-canonical.json +400 -0
  84. package/rules/js/tooling/docs/main.md +53 -0
  85. package/rules/js/tooling/main.mjs +183 -0
  86. package/rules/js/utils_imports/concern.json +4 -0
  87. package/rules/js/utils_imports/docs/main.md +50 -0
  88. package/rules/js/utils_imports/main.mjs +185 -0
  89. package/rules/js/utils_imports/utils_imports.mdc +15 -0
  90. package/rules/js/vscode_extensions/concern.json +11 -0
  91. package/rules/js/vscode_extensions/docs/fix-vscode_extensions.md +24 -0
  92. package/rules/js/vscode_extensions/docs/index.md +11 -0
  93. package/rules/js/vscode_extensions/fix-vscode_extensions.mjs +1 -0
  94. package/rules/js/vscode_extensions/template/extensions.json.snippet.json +6 -0
  95. package/rules/js/vscode_extensions/vscode_extensions.mdc +11 -0
  96. package/rules/js/vscode_extensions/vscode_extensions.rego +12 -0
  97. package/rules/js-bun-db/connection/concern.json +3 -0
  98. package/rules/js-bun-db/connection/connection.mdc +42 -0
  99. package/rules/js-bun-db/docs/index.md +11 -0
  100. package/rules/js-bun-db/lib/bun-sql-scan.mjs +1047 -0
  101. package/rules/js-bun-db/lib/docs/bun-sql-scan.md +63 -0
  102. package/rules/js-bun-db/lib/docs/index.md +11 -0
  103. package/rules/js-bun-db/main.json +1 -0
  104. package/rules/js-bun-db/main.mdc +8 -0
  105. package/rules/js-bun-db/package_json/concern.json +9 -0
  106. package/rules/js-bun-db/package_json/package_json.mdc +31 -0
  107. package/rules/js-bun-db/package_json/package_json.rego +15 -0
  108. package/rules/js-bun-db/package_json/template/package.json.deny.json +6 -0
  109. package/rules/js-bun-db/pg_format_identifiers/concern.json +3 -0
  110. package/rules/js-bun-db/pg_format_identifiers/pg_format_identifiers.mdc +104 -0
  111. package/rules/js-bun-db/safety/concern.json +4 -0
  112. package/rules/js-bun-db/safety/docs/main.md +34 -0
  113. package/rules/js-bun-db/safety/main.mjs +430 -0
  114. package/rules/js-bun-db/safety/safety.mdc +458 -0
  115. package/rules/js-bun-redis/docs/index.md +11 -0
  116. package/rules/js-bun-redis/imports/concern.json +4 -0
  117. package/rules/js-bun-redis/imports/docs/main.md +36 -0
  118. package/rules/js-bun-redis/imports/imports.mdc +47 -0
  119. package/rules/js-bun-redis/imports/main.mjs +88 -0
  120. package/rules/js-bun-redis/lib/docs/index.md +11 -0
  121. package/rules/js-bun-redis/lib/docs/redis-imports.md +227 -0
  122. package/rules/js-bun-redis/lib/redis-imports.mjs +130 -0
  123. package/rules/js-bun-redis/main.json +1 -0
  124. package/rules/js-bun-redis/main.mdc +8 -0
  125. package/rules/js-bun-redis/package_json/concern.json +9 -0
  126. package/rules/js-bun-redis/package_json/package_json.mdc +11 -0
  127. package/rules/js-bun-redis/package_json/package_json.rego +15 -0
  128. package/rules/js-bun-redis/package_json/template/package.json.deny.json +12 -0
  129. package/rules/js-mssql/deps/concern.json +4 -0
  130. package/rules/js-mssql/deps/docs/main.md +33 -0
  131. package/rules/js-mssql/deps/main.mjs +297 -0
  132. package/rules/js-mssql/docs/index.md +11 -0
  133. package/rules/js-mssql/lib/docs/index.md +11 -0
  134. package/rules/js-mssql/lib/docs/mssql-pool-scan.md +380 -0
  135. package/rules/js-mssql/lib/mssql-pool-scan.mjs +610 -0
  136. package/rules/js-mssql/main.json +1 -0
  137. package/rules/js-mssql/main.mdc +144 -0
  138. package/rules/js-mssql/mssql-tvp/concern.json +3 -0
  139. package/rules/js-mssql/mssql-tvp/mssql-tvp.mdc +77 -0
  140. package/rules/js-mssql/package_json/concern.json +9 -0
  141. package/rules/js-mssql/package_json/package_json.mdc +9 -0
  142. package/rules/js-mssql/package_json/package_json.rego +57 -0
  143. package/rules/js-run/configmap/concern.json +9 -0
  144. package/rules/js-run/configmap/configmap.mdc +37 -0
  145. package/rules/js-run/configmap/configmap.rego +21 -0
  146. package/rules/js-run/configmap/template/configmap.yaml.contains.yml +4 -0
  147. package/rules/js-run/docs/index.md +11 -0
  148. package/rules/js-run/jsconfig/concern.json +9 -0
  149. package/rules/js-run/jsconfig/docs/fix-jsconfig.md +28 -0
  150. package/rules/js-run/jsconfig/docs/index.md +9 -0
  151. package/rules/js-run/jsconfig/fix-jsconfig.mjs +119 -0
  152. package/rules/js-run/jsconfig/jsconfig.mdc +48 -0
  153. package/rules/js-run/jsconfig/jsconfig.rego +59 -0
  154. package/rules/js-run/jsconfig/template/jsconfig.json.snippet.json +10 -0
  155. package/rules/js-run/lib/bunyan-imports.mjs +98 -0
  156. package/rules/js-run/lib/check-env-scan.mjs +338 -0
  157. package/rules/js-run/lib/conn-file-rules.mjs +214 -0
  158. package/rules/js-run/lib/conn-imports-scan.mjs +154 -0
  159. package/rules/js-run/lib/docs/bunyan-imports.md +121 -0
  160. package/rules/js-run/lib/docs/check-env-scan.md +438 -0
  161. package/rules/js-run/lib/docs/conn-file-rules.md +304 -0
  162. package/rules/js-run/lib/docs/conn-imports-scan.md +208 -0
  163. package/rules/js-run/lib/docs/index.md +16 -0
  164. package/rules/js-run/lib/docs/promise-settimeout-scan.md +334 -0
  165. package/rules/js-run/lib/docs/temporal-scan.md +29 -0
  166. package/rules/js-run/lib/promise-settimeout-scan.mjs +128 -0
  167. package/rules/js-run/lib/temporal-scan.mjs +52 -0
  168. package/rules/js-run/main.json +1 -0
  169. package/rules/js-run/main.mdc +16 -0
  170. package/rules/js-run/package_json/concern.json +9 -0
  171. package/rules/js-run/package_json/package_json.mdc +44 -0
  172. package/rules/js-run/package_json/package_json.rego +37 -0
  173. package/rules/js-run/package_json/template/package.json.deny.json +22 -0
  174. package/rules/js-run/project-structure/concern.json +3 -0
  175. package/rules/js-run/project-structure/project-structure.mdc +11 -0
  176. package/rules/js-run/runtime/concern.json +12 -0
  177. package/rules/js-run/runtime/docs/fix-runtime.md +27 -0
  178. package/rules/js-run/runtime/docs/main.md +35 -0
  179. package/rules/js-run/runtime/fix-runtime.mjs +46 -0
  180. package/rules/js-run/runtime/main.mjs +496 -0
  181. package/rules/js-run/runtime/runtime.mdc +184 -0
  182. package/rules/js-run/scope/concern.json +3 -0
  183. package/rules/js-run/scope/scope.mdc +11 -0
  184. package/rules/npm-module/docs/index.md +11 -0
  185. package/rules/npm-module/emit_types_config/concern.json +9 -0
  186. package/rules/npm-module/emit_types_config/docs/fix-emit_types_config.md +25 -0
  187. package/rules/npm-module/emit_types_config/docs/index.md +9 -0
  188. package/rules/npm-module/emit_types_config/emit_types_config.mdc +43 -0
  189. package/rules/npm-module/emit_types_config/emit_types_config.rego +28 -0
  190. package/rules/npm-module/emit_types_config/fix-emit_types_config.mjs +5 -0
  191. package/rules/npm-module/emit_types_config/template/tsconfig.emit-types.json.snippet.json +9 -0
  192. package/rules/npm-module/header_doc_pointer/concern.json +5 -0
  193. package/rules/npm-module/header_doc_pointer/docs/main.md +40 -0
  194. package/rules/npm-module/header_doc_pointer/header_doc_pointer.mdc +18 -0
  195. package/rules/npm-module/header_doc_pointer/main.mjs +131 -0
  196. package/rules/npm-module/main.json +1 -0
  197. package/rules/npm-module/main.mdc +36 -0
  198. package/rules/npm-module/npm_package_json/concern.json +9 -0
  199. package/rules/npm-module/npm_package_json/docs/fix-npm_package_json.md +26 -0
  200. package/rules/npm-module/npm_package_json/docs/index.md +9 -0
  201. package/rules/npm-module/npm_package_json/fix-npm_package_json.mjs +5 -0
  202. package/rules/npm-module/npm_package_json/npm_package_json.mdc +57 -0
  203. package/rules/npm-module/npm_package_json/npm_package_json.rego +73 -0
  204. package/rules/npm-module/npm_package_json/template/package.json.snippet.json +1 -0
  205. package/rules/npm-module/package_structure/concern.json +5 -0
  206. package/rules/npm-module/package_structure/docs/main.md +37 -0
  207. package/rules/npm-module/package_structure/main.mjs +448 -0
  208. package/rules/npm-module/package_structure/package_structure.mdc +63 -0
  209. package/rules/npm-module/root_package_json/concern.json +9 -0
  210. package/rules/npm-module/root_package_json/docs/fix-root_package_json.md +25 -0
  211. package/rules/npm-module/root_package_json/docs/index.md +9 -0
  212. package/rules/npm-module/root_package_json/fix-root_package_json.mjs +5 -0
  213. package/rules/npm-module/root_package_json/root_package_json.mdc +41 -0
  214. package/rules/npm-module/root_package_json/root_package_json.rego +28 -0
  215. package/rules/npm-module/root_package_json/template/package.json.snippet.json +1 -0
  216. package/rules/npm-module/rule_meta/concern.json +8 -0
  217. package/rules/npm-module/rule_meta/docs/main.md +35 -0
  218. package/rules/npm-module/rule_meta/main.mjs +119 -0
  219. package/rules/npm-module/rule_meta/rule_meta.mdc +11 -0
  220. package/rules/npm-module/skill_meta/concern.json +5 -0
  221. package/rules/npm-module/skill_meta/docs/main.md +149 -0
  222. package/rules/npm-module/skill_meta/main.mjs +91 -0
  223. package/rules/npm-module/skill_meta/skill_meta.mdc +11 -0
  224. package/rules/tool-surface/docs/index.md +11 -0
  225. package/rules/tool-surface/main.json +6 -0
  226. package/rules/tool-surface/main.mdc +72 -0
  227. package/rules/vue/composition-api/composition-api.mdc +82 -0
  228. package/rules/vue/composition-api/concern.json +3 -0
  229. package/rules/vue/docs/index.md +11 -0
  230. package/rules/vue/lib/docs/index.md +11 -0
  231. package/rules/vue/lib/docs/vue-forbidden-imports.md +265 -0
  232. package/rules/vue/lib/vue-forbidden-imports.mjs +240 -0
  233. package/rules/vue/main.json +1 -0
  234. package/rules/vue/main.mdc +18 -0
  235. package/rules/vue/nheader-layout/concern.json +3 -0
  236. package/rules/vue/nheader-layout/nheader-layout.mdc +171 -0
  237. package/rules/vue/package_json/concern.json +9 -0
  238. package/rules/vue/package_json/package_json.mdc +30 -0
  239. package/rules/vue/package_json/package_json.rego +140 -0
  240. package/rules/vue/packages/concern.json +6 -0
  241. package/rules/vue/packages/docs/index.md +11 -0
  242. package/rules/vue/packages/docs/main.md +35 -0
  243. package/rules/vue/packages/main.mjs +575 -0
  244. package/rules/vue/packages/packages.mdc +56 -0
  245. package/rules/vue/quasar-ui/concern.json +3 -0
  246. package/rules/vue/quasar-ui/quasar-ui.mdc +32 -0
  247. package/rules/vue/structure/concern.json +3 -0
  248. package/rules/vue/structure/structure.mdc +101 -0
  249. package/rules/vue/testing/concern.json +3 -0
  250. package/rules/vue/testing/testing.mdc +40 -0
  251. package/rules/vue/tfm-translations/concern.json +7 -0
  252. package/rules/vue/tfm-translations/docs/main.md +29 -0
  253. package/rules/vue/tfm-translations/main.mjs +55 -0
  254. package/rules/vue/tfm-translations/tfm-translations.mdc +32 -0
  255. package/rules/vue/vite-config/concern.json +3 -0
  256. package/rules/vue/vite-config/vite-config.mdc +153 -0
  257. package/rules/vue/vite-env/concern.json +3 -0
  258. 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,3 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json"
3
+ }
@@ -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
+ {
2
+ "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
3
+ "fixability": "config",
4
+ "policy": {
5
+ "files": {
6
+ "walkGlob": "**/package.json"
7
+ }
8
+ }
9
+ }
@@ -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,9 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
3
+ "fixability": "config",
4
+ "policy": {
5
+ "files": {
6
+ "walkGlob": "**/k8s/*/configmap.yaml"
7
+ }
8
+ }
9
+ }
@@ -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,4 @@
1
+ data:
2
+ OTEL_RESOURCE_ATTRIBUTES:
3
+ - 'service.name='
4
+ - 'service.namespace='
@@ -0,0 +1,11 @@
1
+ ---
2
+ type: Directory Index
3
+ title: npm/rules/js-run
4
+ resource: plugins/lang-js/rules/js-run/
5
+ ---
6
+
7
+ # npm/rules/js-run
8
+
9
+ | Файл | Тип |
10
+ | ------------------- | --------- |
11
+ | [main.mjs](main.md) | JS Module |
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
3
+ "fixability": "config",
4
+ "policy": {
5
+ "files": {
6
+ "walkGlob": "**/jsconfig.json"
7
+ }
8
+ }
9
+ }
@@ -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,9 @@
1
+ ---
2
+ type: Directory Index
3
+ title: npm/rules/js-run/jsconfig
4
+ resource: plugins/lang-js/rules/js-run/jsconfig/
5
+ ---
6
+
7
+ | Файл | Тип |
8
+ | ----------------------------------- | --------- |
9
+ | [fix-jsconfig.mjs](fix-jsconfig.md) | JS Module |
@@ -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.