@7n/rules 1.7.0 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/package.json +1 -1
  3. package/rules/abie/http_route_base/http_route_base.mdc +23 -1
  4. package/rules/adr/madr_format/concern.json +1 -0
  5. package/rules/adr/madr_format/madr_format.mdc +119 -0
  6. package/rules/bun/bunfig/bunfig.mdc +5 -0
  7. package/rules/bun/lint-surface/concern.json +3 -0
  8. package/rules/bun/lint-surface/lint-surface.mdc +13 -0
  9. package/rules/capacitor/platforms/docs/main.md +1 -1
  10. package/rules/capacitor/platforms/main.mjs +5 -1
  11. package/rules/capacitor/platforms/platforms.mdc +108 -0
  12. package/rules/changelog/consistency/comparison-models.mdc +46 -0
  13. package/rules/changelog/consistency/consistency.mdc +35 -0
  14. package/rules/docker/main.mdc +235 -2
  15. package/rules/image-avif/avif_generation/avif_generation.mdc +14 -0
  16. package/rules/js/check/check.mdc +26 -0
  17. package/rules/js/file-extensions/concern.json +3 -0
  18. package/rules/js/file-extensions/file-extensions.mdc +12 -0
  19. package/rules/js/jscpd_config/jscpd_config.mdc +28 -0
  20. package/rules/js/knip/knip.mdc +15 -0
  21. package/rules/js/utils_imports/utils_imports.mdc +15 -0
  22. package/rules/js-bun-db/connection/concern.json +3 -0
  23. package/rules/js-bun-db/connection/connection.mdc +42 -0
  24. package/rules/js-bun-db/package_json/package_json.mdc +15 -1
  25. package/rules/js-bun-db/pg_format_identifiers/concern.json +3 -0
  26. package/rules/js-bun-db/pg_format_identifiers/pg_format_identifiers.mdc +104 -0
  27. package/rules/js-bun-db/safety/safety.mdc +458 -0
  28. package/rules/js-mssql/main.mdc +130 -0
  29. package/rules/js-mssql/mssql-tvp/concern.json +3 -0
  30. package/rules/js-mssql/mssql-tvp/mssql-tvp.mdc +77 -0
  31. package/rules/js-run/configmap/configmap.mdc +6 -0
  32. package/rules/js-run/jsconfig/jsconfig.mdc +23 -0
  33. package/rules/js-run/package_json/package_json.mdc +6 -0
  34. package/rules/js-run/project-structure/concern.json +3 -0
  35. package/rules/js-run/project-structure/project-structure.mdc +11 -0
  36. package/rules/js-run/runtime/runtime.mdc +170 -0
  37. package/rules/js-run/scope/concern.json +3 -0
  38. package/rules/js-run/scope/scope.mdc +11 -0
  39. package/rules/k8s/hasura_configmap/hasura_configmap.mdc +6 -0
  40. package/rules/k8s/hpa_pdb/hpa_pdb.mdc +134 -0
  41. package/rules/k8s/kubeconform/kubeconform.mdc +38 -0
  42. package/rules/k8s/kustomization/kustomization.mdc +73 -0
  43. package/rules/k8s/main.mdc +68 -0
  44. package/rules/k8s/manifest/manifest.mdc +37 -0
  45. package/rules/k8s/manifests/docs/fix-manifests.md +3 -1
  46. package/rules/k8s/manifests/fix-manifests.mjs +11 -0
  47. package/rules/k8s/manifests/main.mjs +28 -0
  48. package/rules/k8s/network_policy/network_policy.mdc +33 -0
  49. package/rules/nginx-default-tpl/http-route/concern.json +1 -0
  50. package/rules/nginx-default-tpl/http-route/http-route.mdc +54 -0
  51. package/rules/nginx-default-tpl/template/template.mdc +152 -0
  52. package/rules/php/tooling/tooling.mdc +7 -6
  53. package/rules/python/pyproject_toml/pyproject_toml.mdc +17 -1
  54. package/rules/python/tooling/tooling.mdc +9 -10
  55. package/rules/rego/main.mdc +14 -0
  56. package/rules/rust/check/check.mdc +16 -0
  57. package/rules/style/admin_table/admin_table.mdc +88 -0
  58. package/rules/style/admin_table/concern.json +7 -0
  59. package/rules/style/admin_table/docs/index.md +9 -0
  60. package/rules/style/admin_table/docs/main.md +14 -0
  61. package/rules/style/admin_table/main.mjs +46 -0
  62. package/rules/style/colors/colors.mdc +21 -0
  63. package/rules/style/colors/concern.json +3 -0
  64. package/rules/style/gap/concern.json +7 -0
  65. package/rules/style/gap/docs/index.md +9 -0
  66. package/rules/style/gap/docs/main.md +15 -0
  67. package/rules/style/gap/gap.mdc +22 -0
  68. package/rules/style/gap/main.mjs +51 -0
  69. package/rules/style/quasar/concern.json +3 -0
  70. package/rules/style/quasar/quasar.mdc +7 -0
  71. package/rules/style/quasar_fixes/concern.json +7 -0
  72. package/rules/style/quasar_fixes/docs/index.md +9 -0
  73. package/rules/style/quasar_fixes/docs/main.md +16 -0
  74. package/rules/style/quasar_fixes/main.mjs +57 -0
  75. package/rules/style/quasar_fixes/quasar_fixes.mdc +32 -0
  76. package/rules/tauri/tool_surface/concern.json +16 -0
  77. package/rules/tauri/tool_surface/docs/index.md +9 -0
  78. package/rules/tauri/tool_surface/docs/main.md +24 -0
  79. package/rules/tauri/tool_surface/main.mjs +145 -0
  80. package/rules/tauri/tool_surface/tool_surface.mdc +29 -0
  81. package/rules/test/vitest-api-conventions/concern.json +7 -0
  82. package/rules/test/vitest-api-conventions/docs/index.md +9 -0
  83. package/rules/test/vitest-api-conventions/docs/main.md +40 -0
  84. package/rules/test/vitest-api-conventions/main.mjs +186 -0
  85. package/rules/test/vitest-api-conventions/vitest-api-conventions.mdc +129 -0
  86. package/rules/text/cspell/cspell.mdc +18 -0
  87. package/rules/text/markdownlint/markdownlint.mdc +4 -0
  88. package/rules/text/run-dotenv-linter/run-dotenv-linter.mdc +17 -0
  89. package/rules/text/run-shellcheck/run-shellcheck.mdc +17 -0
  90. package/rules/text/run-v8r/run-v8r.mdc +23 -0
  91. package/rules/vue/composition-api/composition-api.mdc +82 -0
  92. package/rules/vue/composition-api/concern.json +3 -0
  93. package/rules/vue/main.mdc +1 -1
  94. package/rules/vue/nheader-layout/concern.json +3 -0
  95. package/rules/vue/nheader-layout/nheader-layout.mdc +171 -0
  96. package/rules/vue/packages/packages.mdc +56 -0
  97. package/rules/vue/quasar-ui/concern.json +3 -0
  98. package/rules/vue/quasar-ui/quasar-ui.mdc +32 -0
  99. package/rules/vue/structure/concern.json +3 -0
  100. package/rules/vue/structure/structure.mdc +101 -0
  101. package/rules/vue/testing/concern.json +3 -0
  102. package/rules/vue/testing/testing.mdc +40 -0
  103. package/rules/vue/tfm-translations/concern.json +7 -0
  104. package/rules/vue/tfm-translations/docs/main.md +29 -0
  105. package/rules/vue/tfm-translations/main.mjs +55 -0
  106. package/rules/vue/tfm-translations/tfm-translations.mdc +32 -0
  107. package/rules/vue/vite-config/concern.json +3 -0
  108. package/rules/vue/vite-config/vite-config.mdc +153 -0
  109. package/rules/vue/vite-env/concern.json +3 -0
  110. package/rules/vue/vite-env/vite-env.mdc +61 -0
@@ -0,0 +1,88 @@
1
+ ## Адмінські таблиці — n-admin-table
2
+
3
+ Для таблиць, що мають займати всю висоту сторінки (або блоку), додавай клас `n-admin-table` і атрибути `hide-no-data`, `dense`, `flat`. Таблиця автоматично розтягнеться на весь доступний простір. У блоках меншої висоти — задай їм явний `height`.
4
+
5
+ ```vue
6
+ <!-- ТАБЛИЦЯ -->
7
+ <q-table class="n-admin-table" hide-no-data flat dense />
8
+ ```
9
+
10
+ Якщо клас `.n-admin-table` не визначено в `app.scss` — додай:
11
+
12
+ ```scss
13
+ // ADMIN TABLE
14
+ .n-admin-table {
15
+ height: calc(100vh - 106px);
16
+
17
+ thead {
18
+ position: sticky;
19
+ z-index: 3;
20
+ top: 0;
21
+
22
+ tr th {
23
+ background-color: $grey-3;
24
+ height: 36px !important;
25
+ }
26
+ }
27
+
28
+ // tbody td {
29
+ // font-size: 14px;
30
+ // }
31
+
32
+ tbody tr:last-child td {
33
+ border-bottom: 1px solid rgb(0 0 0 / 12%) !important;
34
+ }
35
+
36
+ th:first-child,
37
+ td:first-child,
38
+ th:last-child,
39
+ td:last-child {
40
+ width: 1px;
41
+ white-space: nowrap;
42
+ }
43
+
44
+ .q-table__bottom {
45
+ background-color: $grey-3;
46
+ }
47
+
48
+ .q-table__progress th {
49
+ height: 1px !important;
50
+ }
51
+
52
+ .text-wrap {
53
+ max-width: 250px;
54
+ white-space: normal;
55
+ }
56
+
57
+ // sticky columns
58
+ td.sticky-left {
59
+ position: sticky;
60
+ left: 0;
61
+ z-index: 2;
62
+ background-color: white;
63
+ box-shadow: 2px 0 5px #0002;
64
+ }
65
+
66
+ td.sticky-right {
67
+ position: sticky;
68
+ right: 0;
69
+ z-index: 2;
70
+ background-color: white;
71
+ box-shadow: -2px 0 5px #0002;
72
+ }
73
+
74
+ th.sticky-left {
75
+ position: sticky;
76
+ left: 0;
77
+ z-index: 3;
78
+ box-shadow: 2px 0 5px #0002;
79
+ }
80
+
81
+ th.sticky-right {
82
+ position: sticky;
83
+ right: 0;
84
+ z-index: 3;
85
+ box-shadow: -2px 0 5px #0002;
86
+ }
87
+ }
88
+ ```
@@ -0,0 +1,7 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
3
+ "lint": {
4
+ "scope": "full",
5
+ "glob": ["**/*.vue", "**/*.scss", "**/*.css"]
6
+ }
7
+ }
@@ -0,0 +1,9 @@
1
+ ---
2
+ type: Directory Index
3
+ title: npm/rules/style/admin_table
4
+ resource: npm/rules/style/admin_table/
5
+ ---
6
+
7
+ | Файл | Тип |
8
+ | ------------------- | --------- |
9
+ | [main.mjs](main.md) | JS Module |
@@ -0,0 +1,14 @@
1
+ ---
2
+ type: JS Module
3
+ title: main.mjs
4
+ resource: npm/rules/style/admin_table/main.mjs
5
+ docgen:
6
+ crc: 9e484318
7
+ ---
8
+
9
+ ## Огляд
10
+
11
+ Детектор concern-а `admin_table` (read-only, whole-repo): якщо десь у `.vue`-файлі
12
+ використано клас `n-admin-table`, він має бути визначений хоч в одному `.scss`/`.css`/`.vue`
13
+ файлі проєкту, інакше — порушення `missing-admin-table-style`. Потребує whole-repo
14
+ сканування (usage і definition можуть бути в різних файлах), тому не залежить від `ctx.files`.
@@ -0,0 +1,46 @@
1
+ /** @see ./docs/main.md */
2
+ import { readFile } from 'node:fs/promises'
3
+
4
+ import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
5
+ import { walkDir } from '../../../scripts/utils/walkDir.mjs'
6
+
7
+ const RELEVANT_RE = /\.(?:vue|scss|css)$/u
8
+ const USAGE_RE = /\bn-admin-table\b/u
9
+ const DEFINITION_RE = /\.n-admin-table\b/u
10
+
11
+ /**
12
+ * Detector style/admin_table (read-only, whole-repo): якщо десь у `.vue` використано
13
+ * клас `n-admin-table`, він має бути визначений хоч в одному `.scss`/`.css`/`.vue`
14
+ * (guide: `admin_table.mdc`). Для усунення false positives — крос-файлова перевірка,
15
+ * тож потребує whole-repo сканування незалежно від `ctx.files`.
16
+ * @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст lint-прогону (cwd)
17
+ * @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат зі зібраними violations
18
+ */
19
+ export async function lint(ctx) {
20
+ const reporter = createViolationReporter(ctx)
21
+ const { fail } = reporter
22
+ const cwd = ctx.cwd
23
+
24
+ /** @type {string[]} */
25
+ const files = []
26
+ await walkDir(cwd, f => {
27
+ if (RELEVANT_RE.test(f)) files.push(f)
28
+ })
29
+
30
+ let used = false
31
+ let defined = false
32
+ for (const file of files) {
33
+ const content = await readFile(file, 'utf8')
34
+ if (!used && file.endsWith('.vue') && USAGE_RE.test(content)) used = true
35
+ if (!defined && DEFINITION_RE.test(content)) defined = true
36
+ if (used && defined) break
37
+ }
38
+
39
+ if (used && !defined) {
40
+ fail(
41
+ 'Клас `.n-admin-table` використовується у `.vue`, але не визначений у жодному `.scss`/`.css` (guide: style/admin_table.mdc) — додай фікс до app.scss',
42
+ 'missing-admin-table-style'
43
+ )
44
+ }
45
+ return reporter.result()
46
+ }
@@ -0,0 +1,21 @@
1
+ ## Кольори: quasar-variables.scss і app.scss
2
+
3
+ Основні кольори визначай виключно через стандартні Quasar-змінні: `$primary`, `$secondary`, `$accent`, `$positive`, `$negative`, `$warning`.
4
+
5
+ Якщо потрібен додатковий колір — визнач його в `quasar-variables.scss`, і **обов'язково** додай до `app.scss` відповідні `.text-*` / `.bg-*` класи:
6
+
7
+ ```scss
8
+ /* quasar-variables.scss */
9
+ $white-a1: color.adjust(white, $alpha: -0.85);
10
+ ```
11
+
12
+ ```scss
13
+ /* app.scss */
14
+ .text-white-a1 {
15
+ color: $white-a1 !important;
16
+ }
17
+
18
+ .bg-white-a1 {
19
+ background-color: $white-a1 !important;
20
+ }
21
+ ```
@@ -0,0 +1,3 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json"
3
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
3
+ "lint": {
4
+ "scope": "full",
5
+ "glob": ["**/*.vue", "**/*.scss", "**/*.css"]
6
+ }
7
+ }
@@ -0,0 +1,9 @@
1
+ ---
2
+ type: Directory Index
3
+ title: npm/rules/style/gap
4
+ resource: npm/rules/style/gap/
5
+ ---
6
+
7
+ | Файл | Тип |
8
+ | ------------------- | --------- |
9
+ | [main.mjs](main.md) | JS Module |
@@ -0,0 +1,15 @@
1
+ ---
2
+ type: JS Module
3
+ title: main.mjs
4
+ resource: npm/rules/style/gap/main.mjs
5
+ docgen:
6
+ crc: dcba23e0
7
+ ---
8
+
9
+ ## Огляд
10
+
11
+ Детектор concern-а `gap` (read-only, whole-repo): кожен суфікс утиліти
12
+ `.n-gap-{xs,sm,md,lg}`, використаний у `.vue`-файлі, має бути визначений хоч в одному
13
+ `.scss`/`.css`/`.vue` файлі проєкту — інакше порушення `missing-gap-style` для цього
14
+ суфіксу. Суфікси відстежуються незалежно один від одного, тож часткове покриття все одно
15
+ виявляє відсутній. Whole-repo сканування, не залежить від `ctx.files`.
@@ -0,0 +1,22 @@
1
+ ## Gap між flex/grid-елементами — .n-gap-*
2
+
3
+ Для відступів між плаваючими елементами (flex, grid) використовуй класи `.n-gap-*` замість `q-gutter-*`. Якщо класів немає в `app.scss` — додай:
4
+
5
+ ```scss
6
+ // GAP
7
+ .n-gap-xs {
8
+ gap: 4px;
9
+ }
10
+
11
+ .n-gap-sm {
12
+ gap: 8px;
13
+ }
14
+
15
+ .n-gap-md {
16
+ gap: 16px;
17
+ }
18
+
19
+ .n-gap-lg {
20
+ gap: 24px;
21
+ }
22
+ ```
@@ -0,0 +1,51 @@
1
+ /** @see ./docs/main.md */
2
+ import { readFile } from 'node:fs/promises'
3
+
4
+ import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
5
+ import { walkDir } from '../../../scripts/utils/walkDir.mjs'
6
+
7
+ const RELEVANT_RE = /\.(?:vue|scss|css)$/u
8
+ const USAGE_RE = /\bn-gap-(xs|sm|md|lg)\b/gu
9
+ const DEFINITION_RE = /\.n-gap-(xs|sm|md|lg)\b/gu
10
+
11
+ /**
12
+ * Detector style/gap (read-only, whole-repo): кожен суфікс `.n-gap-{xs,sm,md,lg}`,
13
+ * використаний у `.vue`, має бути визначений хоч в одному `.scss`/`.css`/`.vue`
14
+ * (guide: `gap.mdc`). Крос-файлова перевірка usage↔definition — потребує whole-repo
15
+ * сканування незалежно від `ctx.files`.
16
+ * @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст lint-прогону (cwd)
17
+ * @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат зі зібраними violations
18
+ */
19
+ export async function lint(ctx) {
20
+ const reporter = createViolationReporter(ctx)
21
+ const { fail } = reporter
22
+ const cwd = ctx.cwd
23
+
24
+ /** @type {string[]} */
25
+ const files = []
26
+ await walkDir(cwd, f => {
27
+ if (RELEVANT_RE.test(f)) files.push(f)
28
+ })
29
+
30
+ /** @type {Set<string>} */
31
+ const used = new Set()
32
+ /** @type {Set<string>} */
33
+ const defined = new Set()
34
+ for (const file of files) {
35
+ const content = await readFile(file, 'utf8')
36
+ if (file.endsWith('.vue')) {
37
+ for (const m of content.matchAll(USAGE_RE)) used.add(m[1])
38
+ }
39
+ for (const m of content.matchAll(DEFINITION_RE)) defined.add(m[1])
40
+ }
41
+
42
+ for (const suffix of used) {
43
+ if (!defined.has(suffix)) {
44
+ fail(
45
+ `Клас \`.n-gap-${suffix}\` використовується у \`.vue\`, але не визначений у жодному \`.scss\`/\`.css\` (guide: style/gap.mdc) — додай клас до app.scss`,
46
+ 'missing-gap-style'
47
+ )
48
+ }
49
+ }
50
+ return reporter.result()
51
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json"
3
+ }
@@ -0,0 +1,7 @@
1
+ ## Quasar як стильова основа
2
+
3
+ У Vue-проектах використовуй **Quasar** як базову стильову систему:
4
+
5
+ - **Класи компонентів:** `q-pa-*`, `q-ma-*`, `q-mt-*`, `q-mb-*`, `row`, `col`, `col-*`, `items-center`, `justify-between`, `text-*`, `bg-*`, `shadow-*` тощо — стандартні утиліти Quasar.
6
+ - **Кольори:** використовуй Quasar CSS-змінні через класи (`text-primary`, `bg-accent`, `text-negative` тощо) або через SCSS-змінні (`$primary`, `$accent`) у `.scss`-файлах.
7
+ - **Не пиши власні утиліти**, якщо є відповідний Quasar-клас.
@@ -0,0 +1,7 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
3
+ "lint": {
4
+ "scope": "full",
5
+ "glob": ["**/*.vue", "**/*.scss", "**/*.css"]
6
+ }
7
+ }
@@ -0,0 +1,9 @@
1
+ ---
2
+ type: Directory Index
3
+ title: npm/rules/style/quasar_fixes
4
+ resource: npm/rules/style/quasar_fixes/
5
+ ---
6
+
7
+ | Файл | Тип |
8
+ | ------------------- | --------- |
9
+ | [main.mjs](main.md) | JS Module |
@@ -0,0 +1,16 @@
1
+ ---
2
+ type: JS Module
3
+ title: main.mjs
4
+ resource: npm/rules/style/quasar_fixes/main.mjs
5
+ docgen:
6
+ crc: 3eecca4c
7
+ ---
8
+
9
+ ## Огляд
10
+
11
+ Детектор concern-а `quasar_fixes` (read-only, whole-repo): якщо `.vue`-файл використовує
12
+ `<q-scroll-area>` або `<q-tooltip>`, десь у `.scss`/`.css`/`.vue` проєкту має бути
13
+ відповідний CSS-фікс (`.q-scrollarea` / `.q-tooltip`) — інакше порушення
14
+ `missing-quasar-fix`. iOS-zoom-фікс (тригер на `input`/`textarea`/`select`) навмисно не
15
+ перевіряється — надто загальний тригер, false-positive на майже будь-якій формі. Whole-repo
16
+ сканування, не залежить від `ctx.files`.
@@ -0,0 +1,57 @@
1
+ /** @see ./docs/main.md */
2
+ import { readFile } from 'node:fs/promises'
3
+
4
+ import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
5
+ import { walkDir } from '../../../scripts/utils/walkDir.mjs'
6
+
7
+ const RELEVANT_RE = /\.(?:vue|scss|css)$/u
8
+
9
+ /**
10
+ * Пари (usage у `.vue` → очікуваний CSS-фікс) з guide `quasar_fixes.mdc`. Навмисно НЕ
11
+ * включено iOS-zoom-фікс (тригер `input`/`textarea`/`select` — занадто загальний,
12
+ * false-positive на майже будь-якій формі; консервативно залишено лише narrative).
13
+ */
14
+ const FIXES = [
15
+ { usage: /<q-scroll-area\b/u, definition: /\.q-scrollarea\b/u, name: 'q-scroll-area', selector: '.q-scrollarea' },
16
+ { usage: /<q-tooltip\b/u, definition: /\.q-tooltip\b/u, name: 'q-tooltip', selector: '.q-tooltip' }
17
+ ]
18
+
19
+ /**
20
+ * Detector style/quasar_fixes (read-only, whole-repo): якщо `.vue` використовує
21
+ * `<q-scroll-area>` або `<q-tooltip>`, у `.scss`/`.css`/`.vue` має бути відповідний
22
+ * CSS-фікс (guide: `quasar_fixes.mdc`). Крос-файлова перевірка usage↔definition —
23
+ * потребує whole-repo сканування незалежно від `ctx.files`.
24
+ * @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст lint-прогону (cwd)
25
+ * @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат зі зібраними violations
26
+ */
27
+ export async function lint(ctx) {
28
+ const reporter = createViolationReporter(ctx)
29
+ const { fail } = reporter
30
+ const cwd = ctx.cwd
31
+
32
+ /** @type {string[]} */
33
+ const files = []
34
+ await walkDir(cwd, f => {
35
+ if (RELEVANT_RE.test(f)) files.push(f)
36
+ })
37
+
38
+ const used = new Set()
39
+ const defined = new Set()
40
+ for (const file of files) {
41
+ const content = await readFile(file, 'utf8')
42
+ for (const fix of FIXES) {
43
+ if (file.endsWith('.vue') && !used.has(fix.name) && fix.usage.test(content)) used.add(fix.name)
44
+ if (!defined.has(fix.name) && fix.definition.test(content)) defined.add(fix.name)
45
+ }
46
+ }
47
+
48
+ for (const fix of FIXES) {
49
+ if (used.has(fix.name) && !defined.has(fix.name)) {
50
+ fail(
51
+ `Компонент \`${fix.name}\` використовується у \`.vue\`, але фікс \`${fix.selector}\` відсутній у \`.scss\`/\`.css\` (guide: style/quasar_fixes.mdc) — додай фікс до app.scss`,
52
+ 'missing-quasar-fix'
53
+ )
54
+ }
55
+ }
56
+ return reporter.result()
57
+ }
@@ -0,0 +1,32 @@
1
+ ## Фікси компонентів Quasar
2
+
3
+ При використанні компонента `q-scroll-area` або `q-tooltip` додай до `app.scss` відповідні фікси. Аналогічно — фікс масштабування на iOS.
4
+
5
+ ```scss
6
+ // FIX стилі для прокручуваної області по висоті екрана
7
+ .q-scrollarea {
8
+ display: flex;
9
+ flex-direction: column;
10
+ height: auto;
11
+ min-height: 0;
12
+ max-height: 100%;
13
+
14
+ .scroll {
15
+ flex: 10000 1 0%;
16
+ }
17
+ }
18
+
19
+ // FIX tooltip
20
+ .q-tooltip {
21
+ font-size: 1em;
22
+ }
23
+
24
+ // FIX не зумувати на iOS
25
+ @media (width <= 600px) {
26
+ input,
27
+ textarea,
28
+ select {
29
+ font-size: 16px;
30
+ }
31
+ }
32
+ ```
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
3
+ "lint": {
4
+ "scope": "full",
5
+ "glob": [
6
+ "**/src/**/*.js",
7
+ "**/src/**/*.ts",
8
+ "**/src/**/*.jsx",
9
+ "**/src/**/*.tsx",
10
+ "**/src/**/*.vue",
11
+ "**/src-tauri/Cargo.toml",
12
+ "**/src-tauri/src/lib.rs",
13
+ "**/src-tauri/capabilities/*.json"
14
+ ]
15
+ }
16
+ }
@@ -0,0 +1,9 @@
1
+ ---
2
+ type: Directory Index
3
+ title: npm/rules/tauri/tool_surface
4
+ resource: npm/rules/tauri/tool_surface/
5
+ ---
6
+
7
+ | Файл | Тип |
8
+ | ------------------- | --------- |
9
+ | [main.mjs](main.md) | JS Module |
@@ -0,0 +1,24 @@
1
+ ---
2
+ type: JS Module
3
+ title: main.mjs
4
+ resource: npm/rules/tauri/tool_surface/main.mjs
5
+ docgen:
6
+ crc: 87b28b8d
7
+ ---
8
+
9
+ ## Огляд
10
+
11
+ Детектор concern-а `tool_surface`: для кожного Tauri-workspace знаходить Tauri-плагіни,
12
+ яких торкається JS/TS/Vue-код (через `@tauri-apps/plugin-*` import або прямий
13
+ `invoke('plugin:<slug>|...')`), і для кожного такого плагіна перевіряє три незалежні умови —
14
+ (1) crate-залежність присутня в `src-tauri/Cargo.toml`, (2) rust-ідентифікатор
15
+ (`tauri_plugin_<slug>`) згаданий у `src-tauri/src/lib.rs` (зареєстрований у builder-і),
16
+ (3) permission для нього є в `src-tauri/capabilities/*.json`. Відсутність будь-якої з трьох —
17
+ тихий рантайм-фейл без помилки компіляції, тому кожна дає окрему violation
18
+ (`tool-surface-plugin-dep-missing` / `-not-registered` / `-capability-missing`).
19
+
20
+ ## Публічний API
21
+
22
+ `PLUGIN_DEP_MISSING`, `PLUGIN_NOT_REGISTERED`, `PLUGIN_CAPABILITY_MISSING` — константи
23
+ причин violation. `lint(ctx)` — точка входу concern-у: обходить усі Tauri app workspaces
24
+ (`findTauriAppWorkspaces`) і перевіряє кожен.
@@ -0,0 +1,145 @@
1
+ /** @see ./docs/main.md */
2
+ import { existsSync } from 'node:fs'
3
+ import { readFile } from 'node:fs/promises'
4
+ import { join } from 'node:path'
5
+
6
+ import { globby } from 'globby'
7
+
8
+ import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
9
+ import { findTauriAppWorkspaces, groupCargoDepsBySection } from '../updater/main.mjs'
10
+
11
+ /** JS торкається плагіна, але crate відсутній у `src-tauri/Cargo.toml`. */
12
+ export const PLUGIN_DEP_MISSING = 'tool-surface-plugin-dep-missing'
13
+ /** JS торкається плагіна, але його rust-ідентифікатор ніде не згадується в `lib.rs`. */
14
+ export const PLUGIN_NOT_REGISTERED = 'tool-surface-plugin-not-registered'
15
+ /** JS торкається плагіна, але жодна `capabilities/*.json` не дає йому permission. */
16
+ export const PLUGIN_CAPABILITY_MISSING = 'tool-surface-plugin-capability-missing'
17
+
18
+ const JS_PLUGIN_IMPORT_RE = /@tauri-apps\/plugin-([a-z0-9-]+)/gu
19
+ const INVOKE_PLUGIN_RE = /invoke\(\s*['"]plugin:([a-z0-9-]+)\|/gu
20
+
21
+ /**
22
+ * Збирає slugs Tauri-плагінів, яких торкається JS/TS/Vue-код через wrapper-пакет
23
+ * `@tauri-apps/plugin-*` (`import ... from '@tauri-apps/plugin-dialog'`) або прямий
24
+ * `invoke('plugin:<slug>|<command>')`. Це "тули" в термінах n-tool-surface — call
25
+ * surface, яким користується UI/оркестратор/LLM.
26
+ * @param {string} srcDir абсолютний шлях до `<ws>/src`
27
+ * @returns {Promise<Set<string>>} множина slugs (`dialog`, `http`, …)
28
+ */
29
+ async function collectPluginUsages(srcDir) {
30
+ const slugs = new Set()
31
+ if (!existsSync(srcDir)) return slugs
32
+
33
+ const files = await globby(['**/*.js', '**/*.ts', '**/*.jsx', '**/*.tsx', '**/*.vue'], {
34
+ cwd: srcDir,
35
+ onlyFiles: true,
36
+ gitignore: false
37
+ })
38
+ for (const file of files) {
39
+ const content = await readFile(join(srcDir, file), 'utf8')
40
+ for (const m of content.matchAll(JS_PLUGIN_IMPORT_RE)) slugs.add(m[1])
41
+ for (const m of content.matchAll(INVOKE_PLUGIN_RE)) slugs.add(m[1])
42
+ }
43
+ return slugs
44
+ }
45
+
46
+ /**
47
+ * Збирає всі permission-ідентифікатори з `capabilities/*.json` workspace-каталогу.
48
+ * @param {string} capDir абсолютний шлях до каталогу capabilities
49
+ * @returns {Promise<Set<string>>} множина permission-ідентифікаторів
50
+ */
51
+ async function collectCapabilityPermissionIds(capDir) {
52
+ const ids = new Set()
53
+ if (!existsSync(capDir)) return ids
54
+
55
+ const files = await globby('*.json', { cwd: capDir, onlyFiles: true, gitignore: false })
56
+ for (const file of files) {
57
+ let cap
58
+ try {
59
+ cap = JSON.parse(await readFile(join(capDir, file), 'utf8'))
60
+ } catch {
61
+ continue
62
+ }
63
+ const perms = Array.isArray(cap.permissions) ? cap.permissions : []
64
+ for (const p of perms) {
65
+ const id = typeof p === 'string' ? p : p?.identifier
66
+ if (id) ids.add(id)
67
+ }
68
+ }
69
+ return ids
70
+ }
71
+
72
+ /**
73
+ * Перевіряє один workspace: кожен Tauri-плагін, якого JS торкається через
74
+ * wrapper-пакет `@tauri-apps/plugin-*` чи прямий `invoke('plugin:...')`, має бути
75
+ * (1) залежністю в Cargo.toml, (2) згаданий у lib.rs (реєстрація builder-а), (3) мати
76
+ * permission у capabilities/*.json — інакше виклик тихо падає в рантаймі, без
77
+ * компіляційної помилки (tauri.mdc tool_surface).
78
+ * @param {string} ws відносний шлях workspace
79
+ * @param {string} cwd корінь репо
80
+ * @param {ReturnType<typeof createViolationReporter>} reporter репортер концерну
81
+ * @returns {Promise<void>} завершується після перевірки
82
+ */
83
+ async function checkWorkspace(ws, cwd, reporter) {
84
+ const base = ws === '.' ? cwd : join(cwd, ws)
85
+ const srcDir = join(base, 'src')
86
+ const usedSlugs = await collectPluginUsages(srcDir)
87
+ if (usedSlugs.size === 0) return
88
+
89
+ const srcTauriDir = join(base, 'src-tauri')
90
+ const cargoPath = join(srcTauriDir, 'Cargo.toml')
91
+ const libRsPath = join(srcTauriDir, 'src', 'lib.rs')
92
+ const capDir = join(srcTauriDir, 'capabilities')
93
+
94
+ const bySection = existsSync(cargoPath) ? groupCargoDepsBySection(await readFile(cargoPath, 'utf8')) : new Map()
95
+ const libRsContent = existsSync(libRsPath) ? await readFile(libRsPath, 'utf8') : ''
96
+ const capIds = await collectCapabilityPermissionIds(capDir)
97
+
98
+ const cargoRel = cargoPath.slice(cwd.length + 1)
99
+ const libRsRel = libRsPath.slice(cwd.length + 1)
100
+ const capRel = capDir.slice(cwd.length + 1)
101
+
102
+ const depKeysList = bySection.values().toArray()
103
+
104
+ for (const slug of [...usedSlugs].toSorted()) {
105
+ const cargoDep = `tauri-plugin-${slug}`
106
+ const rustIdent = `tauri_plugin_${slug.replaceAll('-', '_')}`
107
+
108
+ if (depKeysList.every(keys => !keys.includes(cargoDep))) {
109
+ reporter.fail(
110
+ `${cargoRel}: JS звертається до плагіна "${slug}" (@tauri-apps/plugin-${slug} чи invoke('plugin:${slug}|...')), але "${cargoDep}" відсутній у Cargo.toml (tauri.mdc tool_surface)`,
111
+ { reason: PLUGIN_DEP_MISSING, file: cargoRel, data: { slug } }
112
+ )
113
+ }
114
+
115
+ if (!libRsContent.includes(rustIdent)) {
116
+ reporter.fail(
117
+ `${libRsRel}: плагін "${slug}" ніде не згадується — invoke('plugin:${slug}|...') з UI тихо впаде, бо builder.plugin(${rustIdent}::…) не зареєстрований (tauri.mdc tool_surface)`,
118
+ { reason: PLUGIN_NOT_REGISTERED, file: libRsRel, data: { slug } }
119
+ )
120
+ }
121
+
122
+ if (capIds.values().every(id => id !== slug && !id.startsWith(`${slug}:`))) {
123
+ reporter.fail(
124
+ `${capRel}/*.json: бракує permission "${slug}:*" для плагіна, якого торкається JS — invoke тихо падає permission-denied, видно лише в console.error (tauri.mdc tool_surface)`,
125
+ { reason: PLUGIN_CAPABILITY_MISSING, file: capRel, data: { slug } }
126
+ )
127
+ }
128
+ }
129
+ }
130
+
131
+ /**
132
+ * @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст lint-прогону
133
+ * @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат перевірки
134
+ */
135
+ export async function lint(ctx) {
136
+ const cwd = ctx.cwd
137
+ const reporter = createViolationReporter(ctx)
138
+
139
+ const apps = await findTauriAppWorkspaces(cwd)
140
+ for (const ws of apps) {
141
+ await checkWorkspace(ws, cwd, reporter)
142
+ }
143
+
144
+ return reporter.result()
145
+ }