@7n/rules-lang-js 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/CHANGELOG.md +6 -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 +32 -0
  67. package/rules/js/knip/knip.mdc +15 -0
  68. package/rules/js/knip/main.mjs +68 -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,575 @@
1
+ /** @see ./docs/packages.md */
2
+ import { existsSync, readFileSync } from 'node:fs'
3
+ import { readFile } from 'node:fs/promises'
4
+ import { join, relative } from 'node:path'
5
+
6
+ import { createViolationReporter } from '@7n/rules/scripts/lib/lint-surface/violation-reporter.mjs'
7
+ import {
8
+ findForbiddenNodeImportsInVueFile,
9
+ findForbiddenVueImportsInSourceFile,
10
+ isVueImportScanSourceFile,
11
+ shouldSkipFileForVueImportScan
12
+ } from '../lib/vue-forbidden-imports.mjs'
13
+ import { loadCursorIgnorePaths } from '@7n/rules/scripts/lib/load-cursor-config.mjs'
14
+ import { walkDir } from '@7n/rules/scripts/utils/walkDir.mjs'
15
+ import { getMonorepoPackageRootDirs } from '@7n/rules/scripts/lib/workspaces.mjs'
16
+
17
+ const ESBUILD_RE = /\besbuild\b/
18
+
19
+ /** Регулярний вираз для triple-slash `reference types="vite/client"` у `src/vite-env.d.ts`. */
20
+ const VITE_CLIENT_REFERENCE_RE = /\/\/\/\s*<reference\s+types\s*=\s*["']vite\/client["']\s*\/>/
21
+
22
+ /**
23
+ * Визначає, чи можна сканувати файл як текст на згадки `esbuild`.
24
+ * @param {string} relPosix відносний шлях у posix-форматі
25
+ * @returns {boolean} true якщо файл варто перевірити
26
+ */
27
+ function isEsbuildScanFile(relPosix) {
28
+ if (
29
+ relPosix.startsWith('node_modules/') ||
30
+ relPosix.startsWith('dist/') ||
31
+ relPosix.startsWith('build/') ||
32
+ relPosix.startsWith('coverage/') ||
33
+ relPosix.startsWith('.git/')
34
+ ) {
35
+ return false
36
+ }
37
+
38
+ const lower = relPosix.toLowerCase()
39
+ if (
40
+ lower === 'bun.lock' ||
41
+ lower === 'bun.lockb' ||
42
+ lower === 'package-lock.json' ||
43
+ lower === 'yarn.lock' ||
44
+ lower === 'pnpm-lock.yaml'
45
+ ) {
46
+ return false
47
+ }
48
+
49
+ return (
50
+ lower.endsWith('.js') ||
51
+ lower.endsWith('.mjs') ||
52
+ lower.endsWith('.cjs') ||
53
+ lower.endsWith('.ts') ||
54
+ lower.endsWith('.tsx') ||
55
+ lower.endsWith('.vue') ||
56
+ lower.endsWith('.json') ||
57
+ lower.endsWith('.jsonc') ||
58
+ lower.endsWith('.yaml') ||
59
+ lower.endsWith('.yml') ||
60
+ lower.endsWith('.md') ||
61
+ lower.endsWith('.mdc')
62
+ )
63
+ }
64
+
65
+ /**
66
+ * Збирає `esbuild`-матчі по рядках одного файлу, поки буфер не досягне ліміту.
67
+ * @param {string} rel relative path
68
+ * @param {string} content вміст файлу
69
+ * @param {{ rel: string; line: number; snippet: string }[]} matches буфер для збору матчів
70
+ * @param {number} maxMatches максимум елементів у буфері
71
+ */
72
+ function appendEsbuildLineMatches(rel, content, matches, maxMatches) {
73
+ const lines = content.split('\n')
74
+ for (const [i, line] of lines.entries()) {
75
+ if (matches.length >= maxMatches) return
76
+ if (ESBUILD_RE.test(line)) {
77
+ matches.push({ rel, line: i + 1, snippet: line.trim() })
78
+ }
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Перебирає вибрані файли пакета і збирає до `maxMatches` згадок `esbuild`.
84
+ * @param {string} absPackageRoot абсолютний шлях до кореня пакета
85
+ * @param {{ rel: string }[]} files перелік відносних шляхів
86
+ * @param {number} maxMatches максимум знайдених матчів
87
+ * @returns {Promise<{ rel: string; line: number; snippet: string }[]>} зібрані матчі
88
+ */
89
+ async function collectEsbuildMatchesInFiles(absPackageRoot, files, maxMatches) {
90
+ /** @type {{ rel: string; line: number; snippet: string }[]} */
91
+ const matches = []
92
+ for (const { rel } of files) {
93
+ if (matches.length >= maxMatches) break
94
+ const content = await readFile(join(absPackageRoot, rel), 'utf8')
95
+ if (!ESBUILD_RE.test(content)) continue
96
+ appendEsbuildLineMatches(rel, content, matches, maxMatches)
97
+ }
98
+ return matches
99
+ }
100
+
101
+ /**
102
+ * Сканує дерево пакета на згадки `esbuild` і підказує заміну на `rolldown`.
103
+ * @param {string} rootDir відносний шлях до пакета
104
+ * @param {string} absPackageRoot абсолютний шлях до кореня пакета
105
+ * @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
106
+ * @param {string} prefix параметр prefix
107
+ * @param {(msg: string) => void} passFn callback при успішній перевірці
108
+ * @param {(msg: string) => void} fail callback при помилці
109
+ */
110
+ async function checkEsbuildMentions(rootDir, absPackageRoot, ignorePaths, prefix, passFn, fail) {
111
+ /** @type {{ rel: string }[]} */
112
+ const candidates = []
113
+ await walkDir(
114
+ absPackageRoot,
115
+ absPath => {
116
+ const rel = relative(absPackageRoot, absPath).split('\\').join('/')
117
+ if (!isEsbuildScanFile(rel)) return
118
+ candidates.push({ rel })
119
+ },
120
+ ignorePaths
121
+ )
122
+
123
+ const maxMatches = 30
124
+ const matches = await collectEsbuildMatchesInFiles(absPackageRoot, candidates, maxMatches)
125
+
126
+ if (matches.length === 0) {
127
+ passFn(`${prefix}немає згадок 'esbuild' у джерелах пакета (очікується rolldown)`)
128
+ return
129
+ }
130
+
131
+ for (const m of matches) {
132
+ fail(`${prefix}${m.rel}:${m.line} — знайдено 'esbuild'. Замінити на 'rolldown'. Фрагмент: ${m.snippet}`)
133
+ }
134
+ if (matches.length >= maxMatches) {
135
+ fail(`${prefix}показано перші ${maxMatches} збігів 'esbuild' (замінити на 'rolldown')`)
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Формує зрозумілий для людини підпис пакета для повідомлень перевірки.
141
+ * @param {string} rootDir відносний шлях (`'.'` або `site` тощо)
142
+ * @returns {string} підпис для логів перевірки
143
+ */
144
+ function packageLabel(rootDir) {
145
+ return rootDir === '.' ? 'корінь' : rootDir
146
+ }
147
+
148
+ /**
149
+ * Текст кількості файлів українською (1 файл, 2 файли, 5 файлів, 11 файлів).
150
+ * @param {number} n невід’ємна кількість
151
+ * @returns {string} фраза виду «N файл» / «N файли» / «N файлів»
152
+ */
153
+ function ukFilesCountPhrase(n) {
154
+ const m100 = n % 100
155
+ if (m100 >= 11 && m100 <= 14) {
156
+ return `${n} файлів`
157
+ }
158
+ const m10 = n % 10
159
+ if (m10 === 1) {
160
+ return `${n} файл`
161
+ }
162
+ if (m10 >= 2 && m10 <= 4) {
163
+ return `${n} файли`
164
+ }
165
+ return `${n} файлів`
166
+ }
167
+
168
+ /**
169
+ * Перевіряє `src/vite-env.d.ts` і наявність `jsconfig.json` для підтягування типів асетів Vite у IDE.
170
+ * @param {string} rootDir відносний шлях до кореня пакета
171
+ * @param {string} prefix префікс повідомлень
172
+ * @param {(msg: string) => void} passFn успіх
173
+ * @param {(msg: string) => void} fail помилка
174
+ * @returns {Promise<void>}
175
+ * @param {string} cwd корінь репозиторію
176
+ */
177
+ async function checkViteClientEnvAndEditorConfig(rootDir, prefix, passFn, fail, cwd) {
178
+ const envAbs = join(cwd, rootDir, 'src/vite-env.d.ts')
179
+ if (!existsSync(envAbs)) {
180
+ fail(
181
+ `${prefix}немає src/vite-env.d.ts — додай файл з рядком /// <reference types="vite/client" /> ` +
182
+ `(інакше TS/Volar не бачать типів для імпортів асетів: png, avif, css як URL).`
183
+ )
184
+ return
185
+ }
186
+ const envContent = await readFile(envAbs, 'utf8')
187
+ if (!VITE_CLIENT_REFERENCE_RE.test(envContent)) {
188
+ fail(
189
+ `${prefix}src/vite-env.d.ts має містити /// <reference types="vite/client" /> ` +
190
+ `(без цього імпорти статичних файлів у .vue дають «Cannot find module … type declarations»).`
191
+ )
192
+ return
193
+ }
194
+ passFn(`${prefix}src/vite-env.d.ts посилається на vite/client`)
195
+
196
+ if (!existsSync(join(cwd, rootDir, 'jsconfig.json'))) {
197
+ fail(
198
+ `${prefix}немає jsconfig.json у корені пакета — додай файл з "include": ["src/**/*"] тощо, ` +
199
+ `щоб IDE підхопила vite-env.d.ts і .vue.`
200
+ )
201
+ return
202
+ }
203
+ passFn(`${prefix}jsconfig.json присутній`)
204
+ }
205
+
206
+ /**
207
+ * Чи є пакет бібліотекою компонентів Vue — `vue` оголошено в `peerDependencies`.
208
+ *
209
+ * Такі пакети споживаються Vite-проєктами як залежність; їхні власні джерела **не** проходять
210
+ * через `unplugin-auto-import` споживача (auto-import резолвиться лише в коді самого додатка, не в
211
+ * `node_modules`). Тому в бібліотеці компонентів явні `import { … } from 'vue'` обовʼязкові, і правило
212
+ * авто-імпорту (заборона value-імпортів з `'vue'`) до неї **не** застосовується.
213
+ * @param {{ peerDependencies?: Record<string, string> }} pkg розпарсений package.json
214
+ * @returns {boolean} true, якщо `vue` присутній у `peerDependencies`
215
+ */
216
+ export function isVueComponentLibraryPkg(pkg) {
217
+ return Boolean(pkg?.peerDependencies?.vue)
218
+ }
219
+
220
+ /**
221
+ * Витягує текст аргументів першого виклику `AutoImport(` з vite.config зі збалансованими дужками.
222
+ * Повертає `null`, якщо виклик не знайдено або дужки не збалансовані (тоді перевірка `'vue'`
223
+ * у списку `imports` пропускається — інші чек-сигнали все одно спрацюють).
224
+ * @param {string} content повний текст vite.config
225
+ * @returns {string | null} текст усередині `AutoImport(...)` без зовнішніх дужок, або `null`
226
+ */
227
+ function extractAutoImportCallArgs(content) {
228
+ const marker = 'AutoImport('
229
+ const idx = content.indexOf(marker)
230
+ if (idx === -1) return null
231
+ const start = idx + marker.length
232
+ let depth = 1
233
+ for (let i = start; i < content.length; i++) {
234
+ const ch = content[i]
235
+ if (ch === '(') depth++
236
+ else if (ch === ')') {
237
+ depth--
238
+ if (depth === 0) return content.slice(start, i)
239
+ }
240
+ }
241
+ return null
242
+ }
243
+
244
+ /**
245
+ * Чи передано `'vue'` (або `"vue"`) як рядковий елемент у `imports` всередині виклику `AutoImport(...)`.
246
+ * Без auto-import-ів `vue` забороняти явні value-імпорти `from 'vue'` небезпечно — їх видалення
247
+ * зламає код, бо `ref` / `createApp` тощо більше нікому надати.
248
+ * @param {string} content повний текст vite.config
249
+ * @returns {boolean} true, якщо `AutoImport({ imports: [..., 'vue', ...] })` сконфігуровано
250
+ */
251
+ function viteConfigHasVueInAutoImports(content) {
252
+ const args = extractAutoImportCallArgs(content)
253
+ if (args === null) return false
254
+ return args.includes("'vue'") || args.includes('"vue"')
255
+ }
256
+
257
+ /**
258
+ * Перевіряє vite.config на наявність VueMacros і AutoImport.
259
+ * @param {string} rootDir параметр rootDir
260
+ * @param {boolean} isComponentLibrary чи це бібліотека компонентів (vue у peerDependencies) — тоді auto-import не застосовується
261
+ * @param {string} prefix параметр prefix
262
+ * @param {(msg: string) => void} passFn callback при успішній перевірці
263
+ * @param {(msg: string) => void} fail callback при помилці
264
+ * @returns {Promise<{ hasVueAutoImport: boolean }>} ознака успішно сконфігурованого vue-auto-import (для checkVueImportViolations)
265
+ * @param {string} cwd корінь репозиторію
266
+ */
267
+ async function checkViteConfig(rootDir, isComponentLibrary, prefix, passFn, fail, cwd) {
268
+ const configFiles = ['vite.config.js', 'vite.config.ts', 'vite.config.mjs']
269
+ const viteConfig = configFiles.find(f => existsSync(join(cwd, rootDir, f)))
270
+ if (!viteConfig) {
271
+ fail(`${prefix}немає vite.config.js|ts|mjs у каталозі пакета`)
272
+ return { hasVueAutoImport: false }
273
+ }
274
+ const content = await readFile(join(cwd, rootDir, viteConfig), 'utf8')
275
+ if (ESBUILD_RE.test(content)) {
276
+ fail(`${prefix}${viteConfig} містить 'esbuild' — заміни на 'rolldown'`)
277
+ }
278
+ if (!isComponentLibrary && !content.includes('lightningcss')) {
279
+ fail(
280
+ `${prefix}${viteConfig} не містить css: { transformer: 'lightningcss' } — додай у vite.config і встанови lightningcss у devDependencies (vue.mdc)`
281
+ )
282
+ }
283
+ // VueMacros + AutoImport (і 'vue' у його imports) — інструментарій auto-import Vite-додатка;
284
+ // бібліотека компонентів (vue у peerDependencies) споживається готовою і не потребує цього стеку.
285
+ // npm_lifecycle_event (Bun-compat) перевіряємо нижче незалежно — це не auto-import.
286
+ const hasVueAutoImport = viteConfigHasVueInAutoImports(content)
287
+ if (isComponentLibrary) {
288
+ passFn(
289
+ `${prefix}${viteConfig}: бібліотека компонентів (vue у peerDependencies) — VueMacros/AutoImport не вимагаються`
290
+ )
291
+ } else {
292
+ const checks = [
293
+ { token: 'VueMacros', ok: `${viteConfig} використовує VueMacros`, err: `${viteConfig} не містить VueMacros` },
294
+ { token: 'AutoImport', ok: `${viteConfig} використовує AutoImport`, err: `${viteConfig} не містить AutoImport` }
295
+ ]
296
+ for (const { token, ok, err } of checks) {
297
+ if (content.includes(token)) {
298
+ passFn(`${prefix}${ok}`)
299
+ } else {
300
+ fail(`${prefix}${err}`)
301
+ }
302
+ }
303
+
304
+ if (content.includes('AutoImport(')) {
305
+ if (hasVueAutoImport) {
306
+ passFn(`${prefix}${viteConfig}: AutoImport({ imports: [..., 'vue', ...] }) — value-імпорти з 'vue' покриті`)
307
+ } else {
308
+ fail(
309
+ `${prefix}${viteConfig}: AutoImport не містить 'vue' у imports — додай 'vue' (інакше прибирати ` +
310
+ `value-імпорти на кшталт \`import { ref } from 'vue'\` небезпечно: ref/createApp тощо нікому буде надати)`
311
+ )
312
+ }
313
+ }
314
+ }
315
+
316
+ if (content.includes('process.env.npm_lifecycle_event')) {
317
+ fail(
318
+ `${prefix}${viteConfig} використовує process.env.npm_lifecycle_event — у Bun це не працює. ` +
319
+ `Перенеси логіку на mode (defineConfig(({ mode }) => ...)) і передавай mode в helper-функції.`
320
+ )
321
+ }
322
+
323
+ return { hasVueAutoImport }
324
+ }
325
+
326
+ /**
327
+ * Сканує `.vue` SFC пакета на заборонені імпорти Node-нативних модулів
328
+ * (`node:*` префікс або bare-ім’я вбудованого модуля Node).
329
+ * @param {string} rootDir відносний шлях до пакета
330
+ * @param {string} absPackageRoot абсолютний шлях до кореня пакета
331
+ * @param {string[]} ignorePaths шляхи, які треба пропускати при обході
332
+ * @param {string} prefix префікс повідомлень
333
+ * @param {(msg: string) => void} passFn callback при успішній перевірці
334
+ * @param {(msg: string) => void} fail callback при помилці
335
+ */
336
+ async function checkVueNodeImportViolations(rootDir, absPackageRoot, ignorePaths, prefix, passFn, fail) {
337
+ /** @type {string[]} */
338
+ const vuePaths = []
339
+ await walkDir(
340
+ absPackageRoot,
341
+ absPath => {
342
+ const rel = relative(absPackageRoot, absPath).split('\\').join('/')
343
+ if (!shouldSkipFileForVueImportScan(rel) && rel.endsWith('.vue')) {
344
+ vuePaths.push(absPath)
345
+ }
346
+ },
347
+ ignorePaths
348
+ )
349
+
350
+ let nodeImportViolations = 0
351
+ for (const absPath of vuePaths) {
352
+ const rel = relative(absPackageRoot, absPath).split('\\').join('/')
353
+ const content = await readFile(absPath, 'utf8')
354
+ for (const v of findForbiddenNodeImportsInVueFile(content, rel)) {
355
+ nodeImportViolations++
356
+ fail(
357
+ `${prefix}${rel}:${v.line} — імпорт Node-нативного модуля '${v.specifier}' у .vue заборонено ` +
358
+ `(SFC виконується в браузері, Node API недоступне). Винеси логіку у server-side утіліту. Фрагмент: ${v.snippet}`
359
+ )
360
+ }
361
+ }
362
+ if (nodeImportViolations === 0) {
363
+ passFn(`${prefix}немає імпортів Node-нативних модулів у .vue (проскановано ${ukFilesCountPhrase(vuePaths.length)})`)
364
+ }
365
+ }
366
+
367
+ /**
368
+ * Сканує джерела пакета на заборонені value-імпорти з vue.
369
+ *
370
+ * Якщо `unplugin-auto-import` не сконфігурований на `'vue'` у `vite.config`, явні value-імпорти
371
+ * формально не заборонені — їх видалення зламає код. У цьому випадку перевірка пропускається,
372
+ * а fail про відсутній `'vue'` у `AutoImport.imports` уже зареєстровано в `checkViteConfig`.
373
+ * @param {string} rootDir відносний шлях до пакета
374
+ * @param {string} absPackageRoot абсолютний шлях до кореня пакета
375
+ * @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
376
+ * @param {boolean} isComponentLibrary чи це бібліотека компонентів (vue у peerDependencies) — її джерела не проходять auto-import
377
+ * @param {boolean} hasVueAutoImport чи `AutoImport({ imports: [..., 'vue', ...] })` сконфігуровано
378
+ * @param {string} prefix префікс повідомлення `[<pkg>] `
379
+ * @param {(msg: string) => void} passFn callback при успішній перевірці
380
+ * @param {(msg: string) => void} fail callback при помилці
381
+ */
382
+ async function checkVueImportViolations(
383
+ rootDir,
384
+ absPackageRoot,
385
+ ignorePaths,
386
+ isComponentLibrary,
387
+ hasVueAutoImport,
388
+ prefix,
389
+ passFn,
390
+ fail
391
+ ) {
392
+ if (isComponentLibrary) {
393
+ passFn(
394
+ `${prefix}бібліотека компонентів (vue у peerDependencies) — явні value-імпорти з 'vue' дозволені ` +
395
+ `(джерела не проходять через unplugin-auto-import споживача)`
396
+ )
397
+ return
398
+ }
399
+ if (!hasVueAutoImport) {
400
+ passFn(`${prefix}value-імпорти з 'vue' не заборонені — спершу додай 'vue' до AutoImport.imports у vite.config`)
401
+ return
402
+ }
403
+ /** @type {string[]} */
404
+ const sourcePaths = []
405
+ await walkDir(
406
+ absPackageRoot,
407
+ absPath => {
408
+ const rel = relative(absPackageRoot, absPath).split('\\').join('/')
409
+ if (!shouldSkipFileForVueImportScan(rel) && isVueImportScanSourceFile(rel)) {
410
+ sourcePaths.push(absPath)
411
+ }
412
+ },
413
+ ignorePaths
414
+ )
415
+
416
+ let importViolations = 0
417
+ for (const absPath of sourcePaths) {
418
+ const rel = relative(absPackageRoot, absPath).split('\\').join('/')
419
+ const content = await readFile(absPath, 'utf8')
420
+ for (const v of findForbiddenVueImportsInSourceFile(content, rel)) {
421
+ importViolations++
422
+ fail(`${prefix}${rel}:${v.line} — прибери явний value-імпорт з 'vue' (unplugin-auto-import): ${v.snippet}`)
423
+ }
424
+ }
425
+ if (importViolations === 0) {
426
+ passFn(
427
+ `${prefix}немає заборонених value-імпортів з 'vue' у джерелах (проскановано ${ukFilesCountPhrase(sourcePaths.length)})`
428
+ )
429
+ }
430
+ }
431
+
432
+ /**
433
+ * Перевіряє залежності та vite.config одного Vue-пакета.
434
+ * @param {string} rootDir відносний шлях до пакета
435
+ * @param {boolean} isComponentLibrary чи це бібліотека компонентів (vue у peerDependencies) — auto-import не застосовується
436
+ * @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
437
+ * @param {(msg: string) => void} fail функція зворотного виклику для реєстрації помилки перевірки
438
+ * @param {(msg: string) => void} passFn успішне повідомлення (як у check-reporter)
439
+ * @returns {Promise<void>} завершується після перевірок залежностей, `vite.config` і сканування джерел на імпорти з `vue`
440
+ * @param {string} cwd корінь репозиторію
441
+ */
442
+ async function checkVuePackage(rootDir, isComponentLibrary, ignorePaths, fail, passFn, cwd) {
443
+ const prefix = `[${packageLabel(rootDir)}] `
444
+ passFn(`${prefix}package.json залежності перевіряє npx @7n/rules fix → vue.package_json`)
445
+
446
+ await checkViteClientEnvAndEditorConfig(rootDir, prefix, passFn, fail, cwd)
447
+
448
+ const { hasVueAutoImport } = await checkViteConfig(rootDir, isComponentLibrary, prefix, passFn, fail, cwd)
449
+ await checkVueImportViolations(
450
+ rootDir,
451
+ join(cwd, rootDir),
452
+ ignorePaths,
453
+ isComponentLibrary,
454
+ hasVueAutoImport,
455
+ prefix,
456
+ passFn,
457
+ fail
458
+ )
459
+ await checkVueNodeImportViolations(rootDir, join(cwd, rootDir), ignorePaths, prefix, passFn, fail)
460
+ await checkEsbuildMentions(rootDir, join(cwd, rootDir), ignorePaths, prefix, passFn, fail)
461
+ }
462
+
463
+ /**
464
+ * Збирає корені пакетів, у яких у `dependencies` є `vue`, із ознакою «бібліотека компонентів».
465
+ *
466
+ * Пакети, де `vue` лише в `peerDependencies` (без `dependencies`), — це бібліотеки компонентів, які
467
+ * споживаються Vite-додатками; вони не є самостійними Vite-проєктами, тож app-перевірки (vite-env,
468
+ * VueMacros тощо) до них не застосовуються — їх не збираємо. Якщо ж пакет має `vue` і в
469
+ * `dependencies` (повноцінний проєкт), і в `peerDependencies` — позначаємо `isComponentLibrary`,
470
+ * щоб вимкнути саме правило auto-import (його джерела не проходять через unplugin-auto-import споживача).
471
+ * @param {string[]} roots усі корені пакетів monorepo
472
+ * @returns {Promise<Array<{ rootDir: string, isComponentLibrary: boolean }>>} пакети з vue у dependencies
473
+ * @param {string} cwd корінь репозиторію
474
+ */
475
+ async function collectVueRoots(roots, cwd) {
476
+ /** @type {Array<{ rootDir: string, isComponentLibrary: boolean }>} */
477
+ const vueRoots = []
478
+ for (const r of roots) {
479
+ const p = join(cwd, r, 'package.json')
480
+ if (!existsSync(p)) continue
481
+ const pkg = JSON.parse(await readFile(p, 'utf8'))
482
+ if (pkg.dependencies?.vue) vueRoots.push({ rootDir: r, isComponentLibrary: isVueComponentLibraryPkg(pkg) })
483
+ }
484
+ return vueRoots
485
+ }
486
+
487
+ /**
488
+ * Перевіряє наявність рекомендації `Vue.volar` у `.vscode/extensions.json`.
489
+ * @param {(msg: string) => void} pass pass callback
490
+ * @param {(msg: string) => void} fail fail callback
491
+ * @returns {Promise<void>}
492
+ * @param {string} cwd корінь репозиторію
493
+ */
494
+ async function checkVueVolarRecommendation(pass, fail, cwd) {
495
+ const extPath = join(cwd, '.vscode/extensions.json')
496
+ if (!existsSync(extPath)) {
497
+ fail('.vscode/extensions.json не існує (для Vue-проєкту потрібна рекомендація Vue.volar)')
498
+ return
499
+ }
500
+ const ext = JSON.parse(await readFile(extPath, 'utf8'))
501
+ if (ext.recommendations?.includes('Vue.volar')) {
502
+ pass('extensions.json містить Vue.volar')
503
+ } else {
504
+ fail('extensions.json не містить Vue.volar — додай до recommendations')
505
+ }
506
+ }
507
+
508
+ // Vitest-пакети мусять бути у кореневому devDependencies монорепо,
509
+ // бо npm-module правило забороняє devDeps у published Vue workspace.
510
+ const ROOT_VITEST_DEV_DEPS = ['vitest', '@vitest/coverage-v8', '@stryker-mutator/vitest-runner']
511
+
512
+ /**
513
+ * Перевіряє, що кореневий `package.json` монорепо містить vitest-залежності
514
+ * у `devDependencies`. Викликається тільки коли є Vue-пакети у воркспейсі.
515
+ * @param {string} cwd корінь репозиторію
516
+ * @param {(msg: string) => void} pass pass callback
517
+ * @param {(msg: string) => void} fail fail callback
518
+ * @returns {void}
519
+ */
520
+ function checkRootVitestDevDeps(cwd, pass, fail) {
521
+ const rootPkgPath = join(cwd, 'package.json')
522
+ if (!existsSync(rootPkgPath)) {
523
+ fail('vue: кореневий package.json не знайдено — неможливо перевірити vitest devDependencies')
524
+ return
525
+ }
526
+ let rootPkg
527
+ try {
528
+ rootPkg = JSON.parse(readFileSync(rootPkgPath, 'utf8'))
529
+ } catch {
530
+ fail('vue: кореневий package.json не вдалося розпарсити — неможливо перевірити vitest devDependencies')
531
+ return
532
+ }
533
+ const devDeps =
534
+ rootPkg.devDependencies && typeof rootPkg.devDependencies === 'object' ? Object.keys(rootPkg.devDependencies) : []
535
+ const missing = ROOT_VITEST_DEV_DEPS.filter(p => !devDeps.includes(p))
536
+ if (missing.length === 0) {
537
+ pass(`vue: кореневий devDependencies містить ${ROOT_VITEST_DEV_DEPS.join(', ')} (vue.mdc testing)`)
538
+ } else {
539
+ for (const pkg of missing) {
540
+ fail(
541
+ `vue: кореневий devDependencies не містить '${pkg}' — перенеси з Vue workspace у корінь монорепо (vue.mdc testing)`
542
+ )
543
+ }
544
+ }
545
+ }
546
+
547
+ /**
548
+ * Перевіряє відповідність проєкту правилам vue.mdc (корінь і всі workspace-пакети з `vue` у dependencies).
549
+ * @param {import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст лінту.
550
+ * @returns {Promise<import('@7n/rules/scripts/lib/lint-surface/types.mjs').LintResult>} результат перевірки з порушеннями.
551
+ */
552
+ export async function lint(ctx) {
553
+ const reporter = createViolationReporter(ctx)
554
+ const { pass, fail } = reporter
555
+
556
+ const cwd = ctx.cwd
557
+ const roots = await getMonorepoPackageRootDirs(cwd)
558
+ const vueRoots = await collectVueRoots(roots, cwd)
559
+
560
+ if (vueRoots.length === 0) {
561
+ pass('Vue.volar: пропущено (у repo немає пакетів з vue у dependencies)')
562
+ pass('vue не знайдено в dependencies жодного пакета (перевірка vue пропущена)')
563
+ return reporter.result()
564
+ }
565
+
566
+ await checkVueVolarRecommendation(pass, fail, cwd)
567
+ checkRootVitestDevDeps(cwd, pass, fail)
568
+
569
+ const ignorePaths = await loadCursorIgnorePaths(cwd)
570
+ for (const { rootDir, isComponentLibrary } of vueRoots) {
571
+ await checkVuePackage(rootDir, isComponentLibrary, ignorePaths, fail, pass, cwd)
572
+ }
573
+
574
+ return reporter.result()
575
+ }
@@ -0,0 +1,56 @@
1
+ ## Заборонені імпорти у Vue-пакетах: `vue` value-імпорти та Node-нативні модулі у `.vue`
2
+
3
+ Concern `packages` перевіряє залежності, `vite.config`, `vite-env.d.ts`/`jsconfig.json`, IDE-рекомендації та джерела кожного workspace-пакета з `vue` у `dependencies` (`npm/rules/vue/packages/main.mjs`, повністю описано в `npm/rules/vue/main.mdc`). Цей файл документує детально дві з цих перевірок — заборону value-імпортів з `vue` і заборону імпортів Node-нативних модулів у `.vue` SFC — обидві реалізовані через **oxc-parser** у `npm/rules/vue/lib/vue-forbidden-imports.mjs`.
4
+
5
+ ### Заборона явних value-імпортів з `vue`
6
+
7
+ Потрібно використовувати `unplugin-auto-import` для автоматичного імпортування Vue API. Коли `'vue'` додано до `AutoImport.imports` у `vite.config`, явні value-імпорти вигляду `import { ref, computed } from 'vue'` у файлах додатка **заборонені** — API Vue підставляється автоматично.
8
+
9
+ Дозволені лише:
10
+
11
+ - side-effect: `import 'vue'`
12
+ - type-only: `import type { … } from 'vue'` або `import { type Foo } from 'vue'`
13
+
14
+ ```vue
15
+ <!-- ❌ заборонено (auto-import покриває) -->
16
+ <script setup>
17
+ import { ref, computed, watch, onMounted } from 'vue'
18
+ </script>
19
+
20
+ <!-- ✅ дозволено — не потрібен ручний імпорт -->
21
+ <script setup>
22
+ const count = $ref(0)
23
+ const doubled = $computed(() => count * 2)
24
+ onMounted(() => { /* ... */ })
25
+ </script>
26
+ ```
27
+
28
+ **Виняток:** бібліотеки компонентів (`vue` у `peerDependencies`) — їхні джерела не проходять через `unplugin-auto-import` споживача, тому явні `import { ref } from 'vue'` там обовʼязкові. Визначається через `isVueComponentLibraryPkg` у `npm/rules/vue/packages/main.mjs`. Якщо `vite.config` ще не налаштовано на `'vue'` у `AutoImport.imports` (не бібліотека компонентів) — перевірка value-імпортів теж пропускається, доки не додано `'vue'` до `AutoImport.imports` (інакше видалення імпортів зламає код).
29
+
30
+ Перевірка виконується через **oxc-parser** (`module.staticImports`) у `npm/rules/vue/lib/vue-forbidden-imports.mjs`, функція `findForbiddenVueImportsInSourceFile`. Для `.vue` — лише вміст `<script>` тегів, `<template>` ігнорується.
31
+
32
+ ### Заборонено імпортувати Node-нативні модулі у `.vue` SFC
33
+
34
+ Vue SFC виконується у браузері, тож API Node.js там недоступне. У `<script>` (включно з `<script setup>`)
35
+ заборонено будь-які імпорти вбудованих модулів Node — як з префіксом `node:`, так і bare-ім'ям модуля
36
+ (включно з підшляхами):
37
+
38
+ ```vue title="погано — ламає білд"
39
+ <script setup lang="ts">
40
+ import { setTimeout as sleep } from 'node:timers/promises'
41
+ import fs from 'fs'
42
+ import { readFile } from 'fs/promises'
43
+ import path from 'node:path'
44
+ </script>
45
+ ```
46
+
47
+ Якщо потрібна логіка з Node API — винеси її у server-side утіліту (наприклад, у backend-пакет монорепо)
48
+ та звертайся до неї через HTTP/GraphQL. Браузерні замінники (`window.crypto`, `URL`, `setTimeout` глобальний,
49
+ `AbortController` тощо) використовуй напряму, без import.
50
+
51
+ Правило стосується саме `.vue` файлів. Допоміжні `.ts`/`.js` модулі, які споживаються лише server-side
52
+ (наприклад, окремий пакет утіліт), можуть імпортувати Node-built-ins без обмежень.
53
+
54
+ Перевірка реалізована у `npm/rules/vue/lib/vue-forbidden-imports.mjs` через функцію
55
+ `findForbiddenNodeImportsInVueFile` — аналізує лише `<script>` блоки SFC через **oxc-parser**,
56
+ ігноруючи `<template>`.
@@ -0,0 +1,3 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json"
3
+ }
@@ -0,0 +1,32 @@
1
+ ## Quasar як UI-основа
2
+
3
+ У Vue-проектах використовуй **Quasar** як базовий UI-фреймворк:
4
+
5
+ - **Компоненти:** `q-btn`, `q-input`, `q-select`, `q-table`, `q-dialog`, `q-card`, `q-layout`, `q-page`, `q-drawer` тощо — основа UI.
6
+ - **Плагіни:** `Notify`, `Dialog`, `Loading` та інші Quasar-плагіни.
7
+ - **Кольори:** використовуй Quasar CSS-змінні (`primary`, `secondary`, `accent`, `positive`, `negative`, `warning`, `info`, `dark`) і утиліти (`text-primary`, `bg-accent` тощо).
8
+ - **Утиліти:** flex-layout (`row`, `col`, `items-center`), spacing (`q-pa-md`, `q-mt-sm`), shadow (`shadow-2`) — зі стандартної бібліотеки Quasar.
9
+ - **Кастомні компоненти** `@nitra/components` — **надбудова** над Quasar, а не заміна; їх слід надавати перевагу лише там, де вони є.
10
+
11
+ ## @nitra/components — надавай перевагу перед Quasar-компонентами
12
+
13
+ При створенні нової функціональності використовуй компоненти `@nitra/components`, якщо логіка компонента дозволяє отримати потрібний функціонал. Заміни:
14
+
15
+ | Завдання | `@nitra/components` | Замість Quasar |
16
+ | --- | --- | --- |
17
+ | Діалоги | `NDialog` | `q-dialog` |
18
+ | Multi-вибір | `NSelectMulti` | `q-select` (multiple) |
19
+ | Текстовий редактор | `NEditor` | `q-editor` |
20
+ | Вибір дати | `NDate` | — |
21
+ | Вибір місяця/року | `NDateMonthYear` | — |
22
+ | Діапазон дат | `NDateRange` | — |
23
+ | Дата і час | `NDateTime` | — |
24
+ | Drag&drop список | `NDraggableList` | — |
25
+ | Редаговане значення | `NEditableString` | — |
26
+ | Повідомлення | `NCallout` | — |
27
+ | Хедер проекту | `NHeader` | — |
28
+ | Перемикання мов | `NLang` | — (якщо `NHeader` не використовується) |
29
+ | Меню проекту | `NMenu` | — |
30
+ | Зображення (масив / одиночне) | `NImages` | — |
31
+ | Завантаження файлів | `NUploader` | — |
32
+ | Вибір колонок `q-table` | `NTableColumns` | — |
@@ -0,0 +1,3 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json"
3
+ }