@7n/rules-lang-js 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/package.json +14 -4
- package/rules/bun/bunfig/bunfig.mdc +17 -0
- package/rules/bun/bunfig/bunfig.rego +29 -0
- package/rules/bun/bunfig/concern.json +9 -0
- package/rules/bun/bunfig/template/bunfig.toml.snippet.toml +2 -0
- package/rules/bun/docs/index.md +11 -0
- package/rules/bun/layout/concern.json +16 -0
- package/rules/bun/layout/docs/fix-layout.md +29 -0
- package/rules/bun/layout/docs/main.md +34 -0
- package/rules/bun/layout/fix-layout.mjs +63 -0
- package/rules/bun/layout/layout.mdc +60 -0
- package/rules/bun/layout/main.mjs +53 -0
- package/rules/bun/licensee/concern.json +7 -0
- package/rules/bun/licensee/docs/fix-licensee.md +27 -0
- package/rules/bun/licensee/docs/index.md +12 -0
- package/rules/bun/licensee/docs/main.md +30 -0
- package/rules/bun/licensee/fix-licensee.mjs +31 -0
- package/rules/bun/licensee/main.mjs +68 -0
- package/rules/bun/lint-surface/concern.json +3 -0
- package/rules/bun/lint-surface/lint-surface.mdc +13 -0
- package/rules/bun/main.json +1 -0
- package/rules/bun/main.mdc +11 -0
- package/rules/bun/package_json/concern.json +9 -0
- package/rules/bun/package_json/docs/fix-package_json.md +28 -0
- package/rules/bun/package_json/docs/index.md +9 -0
- package/rules/bun/package_json/fix-package_json.mjs +311 -0
- package/rules/bun/package_json/package_json.mdc +14 -0
- package/rules/bun/package_json/package_json.rego +64 -0
- package/rules/bun/package_json/template/package.json.deny.json +4 -0
- package/rules/js/check/check.mdc +26 -0
- package/rules/js/check/concern.json +19 -0
- package/rules/js/check/docs/eslint-config.md +56 -0
- package/rules/js/check/docs/fix-check.md +48 -0
- package/rules/js/check/docs/index.md +13 -0
- package/rules/js/check/docs/main.md +48 -0
- package/rules/js/check/eslint-config.mjs +262 -0
- package/rules/js/check/fix-check.mjs +82 -0
- package/rules/js/check/main.mjs +310 -0
- package/rules/js/dep-policy/concern.json +4 -0
- package/rules/js/dep-policy/dep-policy.mdc +36 -0
- package/rules/js/dep-policy/docs/main.md +35 -0
- package/rules/js/dep-policy/main.mjs +99 -0
- package/rules/js/docs/index.md +11 -0
- package/rules/js/eslint/concern.json +8 -0
- package/rules/js/eslint/docs/fix-eslint.md +50 -0
- package/rules/js/eslint/docs/fix-worker.md +31 -0
- package/rules/js/eslint/docs/index.md +11 -0
- package/rules/js/eslint/docs/main.md +35 -0
- package/rules/js/eslint/fix-eslint.mjs +160 -0
- package/rules/js/eslint/fix-worker.mjs +139 -0
- package/rules/js/eslint/main.mjs +115 -0
- package/rules/js/file-extensions/concern.json +3 -0
- package/rules/js/file-extensions/file-extensions.mdc +12 -0
- package/rules/js/jscpd_config/concern.json +11 -0
- package/rules/js/jscpd_config/docs/fix-jscpd_config.md +25 -0
- package/rules/js/jscpd_config/docs/index.md +9 -0
- package/rules/js/jscpd_config/fix-jscpd_config.mjs +3 -0
- package/rules/js/jscpd_config/jscpd_config.mdc +42 -0
- package/rules/js/jscpd_config/jscpd_config.rego +44 -0
- package/rules/js/jscpd_config/template/.jscpd.json.snippet.json +7 -0
- package/rules/js/jscpd_duplicates/concern.json +7 -0
- package/rules/js/jscpd_duplicates/docs/main.md +29 -0
- package/rules/js/jscpd_duplicates/main.mjs +69 -0
- package/rules/js/knip/concern.json +7 -0
- package/rules/js/knip/docs/main.md +32 -0
- package/rules/js/knip/knip.mdc +15 -0
- package/rules/js/knip/main.mjs +68 -0
- package/rules/js/lint-findings/concern.json +3 -0
- package/rules/js/lint-findings/docs/main.md +40 -0
- package/rules/js/lint-findings/main.mjs +125 -0
- package/rules/js/main.json +1 -0
- package/rules/js/main.mdc +18 -0
- package/rules/js/package_json/concern.json +9 -0
- package/rules/js/package_json/docs/fix-package_json.md +25 -0
- package/rules/js/package_json/docs/index.md +9 -0
- package/rules/js/package_json/fix-package_json.mjs +3 -0
- package/rules/js/package_json/package_json.mdc +15 -0
- package/rules/js/package_json/package_json.rego +142 -0
- package/rules/js/package_json/template/package.json.snippet.json +6 -0
- package/rules/js/tooling/concern.json +3 -0
- package/rules/js/tooling/data/tooling/knip-canonical.json +30 -0
- package/rules/js/tooling/data/tooling/oxlint-canonical.json +400 -0
- package/rules/js/tooling/docs/main.md +53 -0
- package/rules/js/tooling/main.mjs +183 -0
- package/rules/js/utils_imports/concern.json +4 -0
- package/rules/js/utils_imports/docs/main.md +50 -0
- package/rules/js/utils_imports/main.mjs +185 -0
- package/rules/js/utils_imports/utils_imports.mdc +15 -0
- package/rules/js/vscode_extensions/concern.json +11 -0
- package/rules/js/vscode_extensions/docs/fix-vscode_extensions.md +24 -0
- package/rules/js/vscode_extensions/docs/index.md +11 -0
- package/rules/js/vscode_extensions/fix-vscode_extensions.mjs +1 -0
- package/rules/js/vscode_extensions/template/extensions.json.snippet.json +6 -0
- package/rules/js/vscode_extensions/vscode_extensions.mdc +11 -0
- package/rules/js/vscode_extensions/vscode_extensions.rego +12 -0
- package/rules/js-bun-db/connection/concern.json +3 -0
- package/rules/js-bun-db/connection/connection.mdc +42 -0
- package/rules/js-bun-db/docs/index.md +11 -0
- package/rules/js-bun-db/lib/bun-sql-scan.mjs +1047 -0
- package/rules/js-bun-db/lib/docs/bun-sql-scan.md +63 -0
- package/rules/js-bun-db/lib/docs/index.md +11 -0
- package/rules/js-bun-db/main.json +1 -0
- package/rules/js-bun-db/main.mdc +8 -0
- package/rules/js-bun-db/package_json/concern.json +9 -0
- package/rules/js-bun-db/package_json/package_json.mdc +31 -0
- package/rules/js-bun-db/package_json/package_json.rego +15 -0
- package/rules/js-bun-db/package_json/template/package.json.deny.json +6 -0
- package/rules/js-bun-db/pg_format_identifiers/concern.json +3 -0
- package/rules/js-bun-db/pg_format_identifiers/pg_format_identifiers.mdc +104 -0
- package/rules/js-bun-db/safety/concern.json +4 -0
- package/rules/js-bun-db/safety/docs/main.md +34 -0
- package/rules/js-bun-db/safety/main.mjs +430 -0
- package/rules/js-bun-db/safety/safety.mdc +458 -0
- package/rules/js-bun-redis/docs/index.md +11 -0
- package/rules/js-bun-redis/imports/concern.json +4 -0
- package/rules/js-bun-redis/imports/docs/main.md +36 -0
- package/rules/js-bun-redis/imports/imports.mdc +47 -0
- package/rules/js-bun-redis/imports/main.mjs +88 -0
- package/rules/js-bun-redis/lib/docs/index.md +11 -0
- package/rules/js-bun-redis/lib/docs/redis-imports.md +227 -0
- package/rules/js-bun-redis/lib/redis-imports.mjs +130 -0
- package/rules/js-bun-redis/main.json +1 -0
- package/rules/js-bun-redis/main.mdc +8 -0
- package/rules/js-bun-redis/package_json/concern.json +9 -0
- package/rules/js-bun-redis/package_json/package_json.mdc +11 -0
- package/rules/js-bun-redis/package_json/package_json.rego +15 -0
- package/rules/js-bun-redis/package_json/template/package.json.deny.json +12 -0
- package/rules/js-mssql/deps/concern.json +4 -0
- package/rules/js-mssql/deps/docs/main.md +33 -0
- package/rules/js-mssql/deps/main.mjs +297 -0
- package/rules/js-mssql/docs/index.md +11 -0
- package/rules/js-mssql/lib/docs/index.md +11 -0
- package/rules/js-mssql/lib/docs/mssql-pool-scan.md +380 -0
- package/rules/js-mssql/lib/mssql-pool-scan.mjs +610 -0
- package/rules/js-mssql/main.json +1 -0
- package/rules/js-mssql/main.mdc +144 -0
- package/rules/js-mssql/mssql-tvp/concern.json +3 -0
- package/rules/js-mssql/mssql-tvp/mssql-tvp.mdc +77 -0
- package/rules/js-mssql/package_json/concern.json +9 -0
- package/rules/js-mssql/package_json/package_json.mdc +9 -0
- package/rules/js-mssql/package_json/package_json.rego +57 -0
- package/rules/js-run/configmap/concern.json +9 -0
- package/rules/js-run/configmap/configmap.mdc +37 -0
- package/rules/js-run/configmap/configmap.rego +21 -0
- package/rules/js-run/configmap/template/configmap.yaml.contains.yml +4 -0
- package/rules/js-run/docs/index.md +11 -0
- package/rules/js-run/jsconfig/concern.json +9 -0
- package/rules/js-run/jsconfig/docs/fix-jsconfig.md +28 -0
- package/rules/js-run/jsconfig/docs/index.md +9 -0
- package/rules/js-run/jsconfig/fix-jsconfig.mjs +119 -0
- package/rules/js-run/jsconfig/jsconfig.mdc +48 -0
- package/rules/js-run/jsconfig/jsconfig.rego +59 -0
- package/rules/js-run/jsconfig/template/jsconfig.json.snippet.json +10 -0
- package/rules/js-run/lib/bunyan-imports.mjs +98 -0
- package/rules/js-run/lib/check-env-scan.mjs +338 -0
- package/rules/js-run/lib/conn-file-rules.mjs +214 -0
- package/rules/js-run/lib/conn-imports-scan.mjs +154 -0
- package/rules/js-run/lib/docs/bunyan-imports.md +121 -0
- package/rules/js-run/lib/docs/check-env-scan.md +438 -0
- package/rules/js-run/lib/docs/conn-file-rules.md +304 -0
- package/rules/js-run/lib/docs/conn-imports-scan.md +208 -0
- package/rules/js-run/lib/docs/index.md +16 -0
- package/rules/js-run/lib/docs/promise-settimeout-scan.md +334 -0
- package/rules/js-run/lib/docs/temporal-scan.md +29 -0
- package/rules/js-run/lib/promise-settimeout-scan.mjs +128 -0
- package/rules/js-run/lib/temporal-scan.mjs +52 -0
- package/rules/js-run/main.json +1 -0
- package/rules/js-run/main.mdc +16 -0
- package/rules/js-run/package_json/concern.json +9 -0
- package/rules/js-run/package_json/package_json.mdc +44 -0
- package/rules/js-run/package_json/package_json.rego +37 -0
- package/rules/js-run/package_json/template/package.json.deny.json +22 -0
- package/rules/js-run/project-structure/concern.json +3 -0
- package/rules/js-run/project-structure/project-structure.mdc +11 -0
- package/rules/js-run/runtime/concern.json +12 -0
- package/rules/js-run/runtime/docs/fix-runtime.md +27 -0
- package/rules/js-run/runtime/docs/main.md +35 -0
- package/rules/js-run/runtime/fix-runtime.mjs +46 -0
- package/rules/js-run/runtime/main.mjs +496 -0
- package/rules/js-run/runtime/runtime.mdc +184 -0
- package/rules/js-run/scope/concern.json +3 -0
- package/rules/js-run/scope/scope.mdc +11 -0
- package/rules/npm-module/docs/index.md +11 -0
- package/rules/npm-module/emit_types_config/concern.json +9 -0
- package/rules/npm-module/emit_types_config/docs/fix-emit_types_config.md +25 -0
- package/rules/npm-module/emit_types_config/docs/index.md +9 -0
- package/rules/npm-module/emit_types_config/emit_types_config.mdc +43 -0
- package/rules/npm-module/emit_types_config/emit_types_config.rego +28 -0
- package/rules/npm-module/emit_types_config/fix-emit_types_config.mjs +5 -0
- package/rules/npm-module/emit_types_config/template/tsconfig.emit-types.json.snippet.json +9 -0
- package/rules/npm-module/header_doc_pointer/concern.json +5 -0
- package/rules/npm-module/header_doc_pointer/docs/main.md +40 -0
- package/rules/npm-module/header_doc_pointer/header_doc_pointer.mdc +18 -0
- package/rules/npm-module/header_doc_pointer/main.mjs +131 -0
- package/rules/npm-module/main.json +1 -0
- package/rules/npm-module/main.mdc +36 -0
- package/rules/npm-module/npm_package_json/concern.json +9 -0
- package/rules/npm-module/npm_package_json/docs/fix-npm_package_json.md +26 -0
- package/rules/npm-module/npm_package_json/docs/index.md +9 -0
- package/rules/npm-module/npm_package_json/fix-npm_package_json.mjs +5 -0
- package/rules/npm-module/npm_package_json/npm_package_json.mdc +57 -0
- package/rules/npm-module/npm_package_json/npm_package_json.rego +73 -0
- package/rules/npm-module/npm_package_json/template/package.json.snippet.json +1 -0
- package/rules/npm-module/package_structure/concern.json +5 -0
- package/rules/npm-module/package_structure/docs/main.md +37 -0
- package/rules/npm-module/package_structure/main.mjs +448 -0
- package/rules/npm-module/package_structure/package_structure.mdc +63 -0
- package/rules/npm-module/root_package_json/concern.json +9 -0
- package/rules/npm-module/root_package_json/docs/fix-root_package_json.md +25 -0
- package/rules/npm-module/root_package_json/docs/index.md +9 -0
- package/rules/npm-module/root_package_json/fix-root_package_json.mjs +5 -0
- package/rules/npm-module/root_package_json/root_package_json.mdc +41 -0
- package/rules/npm-module/root_package_json/root_package_json.rego +28 -0
- package/rules/npm-module/root_package_json/template/package.json.snippet.json +1 -0
- package/rules/npm-module/rule_meta/concern.json +8 -0
- package/rules/npm-module/rule_meta/docs/main.md +35 -0
- package/rules/npm-module/rule_meta/main.mjs +119 -0
- package/rules/npm-module/rule_meta/rule_meta.mdc +11 -0
- package/rules/npm-module/skill_meta/concern.json +5 -0
- package/rules/npm-module/skill_meta/docs/main.md +149 -0
- package/rules/npm-module/skill_meta/main.mjs +91 -0
- package/rules/npm-module/skill_meta/skill_meta.mdc +11 -0
- package/rules/tool-surface/docs/index.md +11 -0
- package/rules/tool-surface/main.json +6 -0
- package/rules/tool-surface/main.mdc +72 -0
- package/rules/vue/composition-api/composition-api.mdc +82 -0
- package/rules/vue/composition-api/concern.json +3 -0
- package/rules/vue/docs/index.md +11 -0
- package/rules/vue/lib/docs/index.md +11 -0
- package/rules/vue/lib/docs/vue-forbidden-imports.md +265 -0
- package/rules/vue/lib/vue-forbidden-imports.mjs +240 -0
- package/rules/vue/main.json +1 -0
- package/rules/vue/main.mdc +18 -0
- package/rules/vue/nheader-layout/concern.json +3 -0
- package/rules/vue/nheader-layout/nheader-layout.mdc +171 -0
- package/rules/vue/package_json/concern.json +9 -0
- package/rules/vue/package_json/package_json.mdc +30 -0
- package/rules/vue/package_json/package_json.rego +140 -0
- package/rules/vue/packages/concern.json +6 -0
- package/rules/vue/packages/docs/index.md +11 -0
- package/rules/vue/packages/docs/main.md +35 -0
- package/rules/vue/packages/main.mjs +575 -0
- package/rules/vue/packages/packages.mdc +56 -0
- package/rules/vue/quasar-ui/concern.json +3 -0
- package/rules/vue/quasar-ui/quasar-ui.mdc +32 -0
- package/rules/vue/structure/concern.json +3 -0
- package/rules/vue/structure/structure.mdc +101 -0
- package/rules/vue/testing/concern.json +3 -0
- package/rules/vue/testing/testing.mdc +40 -0
- package/rules/vue/tfm-translations/concern.json +7 -0
- package/rules/vue/tfm-translations/docs/main.md +29 -0
- package/rules/vue/tfm-translations/main.mjs +55 -0
- package/rules/vue/tfm-translations/tfm-translations.mdc +32 -0
- package/rules/vue/vite-config/concern.json +3 -0
- package/rules/vue/vite-config/vite-config.mdc +153 -0
- package/rules/vue/vite-env/concern.json +3 -0
- package/rules/vue/vite-env/vite-env.mdc +61 -0
|
@@ -0,0 +1,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,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` | — |
|