@nitra/cursor 3.22.0 → 3.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.pi-template/extensions/n-cursor-adr/docs/index.md +181 -0
- package/AGENTS.template.md +4 -0
- package/CHANGELOG.md +37 -3
- package/bin/docs/n-cursor.md +636 -0
- package/bin/docs/rename-yaml-extensions.md +207 -0
- package/bin/n-cursor.js +30 -3
- package/package.json +1 -1
- package/rules/abie/docs/fix.md +18 -0
- package/rules/abie/js/docs/applies.md +26 -0
- package/rules/abie/js/docs/env_dns.md +32 -0
- package/rules/abie/js/docs/firebase_hosting.md +23 -0
- package/rules/abie/js/docs/hc_pairing.md +35 -0
- package/rules/abie/js/docs/ua_http_route.md +28 -0
- package/rules/abie/js/docs/ua_node_selector.md +28 -0
- package/rules/abie/lib/docs/enabled.md +29 -0
- package/rules/abie/lib/docs/env-dns.md +35 -0
- package/rules/abie/lib/docs/hc-yaml.md +33 -0
- package/rules/abie/lib/docs/http-route.md +44 -0
- package/rules/abie/lib/docs/k8s-tree.md +40 -0
- package/rules/abie/lib/docs/kustomization-patches.md +47 -0
- package/rules/abie/lib/docs/overlay-paths.md +38 -0
- package/rules/abie/lib/docs/yaml.md +29 -0
- package/rules/adr/docs/fix.md +148 -0
- package/rules/adr/js/docs/hooks.md +259 -0
- package/rules/bun/docs/fix.md +156 -0
- package/rules/bun/js/docs/layout.md +393 -0
- package/rules/capacitor/docs/fix.md +121 -0
- package/rules/capacitor/js/docs/platforms.md +295 -0
- package/rules/changelog/changelog.mdc +4 -2
- package/rules/changelog/docs/fix.md +174 -0
- package/rules/changelog/js/consistency.mjs +114 -13
- package/rules/changelog/js/docs/consistency.md +387 -0
- package/rules/changelog/lib/docs/package-manifest.md +210 -0
- package/rules/ci4/docs/fix.md +179 -0
- package/rules/ci4/js/docs/marksman_config.md +128 -0
- package/rules/docker/docker.mdc +8 -3
- package/rules/docker/docs/fix.md +171 -0
- package/rules/docker/js/docs/lint.md +258 -0
- package/rules/docker/lib/docs/docker-hadolint.md +184 -0
- package/rules/docker/lib/docs/docker-mirror.md +247 -0
- package/rules/docker/lib/docs/docker-native-addon.md +170 -0
- package/rules/docker/lib/docs/docker-nginx-user.md +219 -0
- package/rules/docker/lint/docs/lint.md +193 -0
- package/rules/efes/docs/fix.md +203 -0
- package/rules/feedback/docs/fix.md +140 -0
- package/rules/flow/docs/fix.md +152 -0
- package/rules/ga/docs/fix.md +158 -0
- package/rules/ga/js/docs/lint.md +100 -0
- package/rules/ga/js/docs/workflows.md +217 -0
- package/rules/ga/lint/docs/lint.md +209 -0
- package/rules/ga/policy/clean_merged_branch/clean_merged_branch.rego +11 -2
- package/rules/ga/policy/clean_merged_branch/template/clean-merged-branch.yml.snippet.yml +3 -4
- package/rules/graphql/docs/fix.md +126 -0
- package/rules/graphql/js/docs/tooling.md +264 -0
- package/rules/graphql/lib/docs/graphql-gql-scan.md +219 -0
- package/rules/hasura/docs/fix.md +120 -0
- package/rules/hasura/hasura.mdc +14 -0
- package/rules/hasura/js/docs/internal_urls.md +326 -0
- package/rules/image-avif/docs/fix.md +132 -0
- package/rules/image-avif/js/docs/avif_generation.md +241 -0
- package/rules/image-compress/docs/fix.md +150 -0
- package/rules/image-compress/js/docs/package_setup.md +191 -0
- package/rules/js-bun-db/docs/fix.md +148 -0
- package/rules/js-bun-db/js/docs/safety.md +231 -0
- package/rules/js-bun-db/js-bun-db.mdc +42 -13
- package/rules/js-bun-db/lib/docs/bun-sql-scan.md +347 -0
- package/rules/js-bun-redis/docs/fix.md +123 -0
- package/rules/js-bun-redis/js/docs/imports.md +176 -0
- package/rules/js-bun-redis/lib/docs/redis-imports.md +223 -0
- package/rules/js-lint/docs/fix.md +117 -0
- package/rules/js-lint/js/docs/lint.md +250 -0
- package/rules/js-lint/js/docs/tooling.md +348 -0
- package/rules/js-lint/js/docs/utils_imports.md +207 -0
- package/rules/js-lint-ci/docs/fix.md +154 -0
- package/rules/js-lint-ci/js/docs/lint.md +144 -0
- package/rules/js-mssql/docs/fix.md +128 -0
- package/rules/js-mssql/js/docs/deps.md +263 -0
- package/rules/js-mssql/lib/docs/mssql-pool-scan.md +367 -0
- package/rules/js-run/docs/fix.md +144 -0
- package/rules/js-run/js/docs/runtime.md +388 -0
- package/rules/js-run/lib/docs/bunyan-imports.md +117 -0
- package/rules/js-run/lib/docs/check-env-scan.md +433 -0
- package/rules/js-run/lib/docs/conn-file-rules.md +300 -0
- package/rules/js-run/lib/docs/conn-imports-scan.md +204 -0
- package/rules/js-run/lib/docs/promise-settimeout-scan.md +326 -0
- package/rules/k8s/docs/fix.md +129 -0
- package/rules/k8s/js/docs/manifests.md +344 -0
- package/rules/k8s/js/manifests.mjs +6 -2
- package/rules/k8s/k8s.mdc +4 -2
- package/rules/k8s/lint/docs/lint.md +411 -0
- package/rules/k8s/policy/network_policy/template/deployment.snippet.yaml +2 -0
- package/rules/k8s/policy/network_policy/template/stateful-set.snippet.yaml +2 -0
- package/rules/nginx-default-tpl/docs/fix.md +124 -0
- package/rules/nginx-default-tpl/js/docs/template.md +378 -0
- package/rules/npm-module/docs/fix.md +98 -0
- package/rules/npm-module/js/docs/package_structure.md +274 -0
- package/rules/npm-module/js/docs/rule_meta.md +137 -0
- package/rules/npm-module/js/docs/skill_meta.md +190 -0
- package/rules/php/docs/fix.md +107 -0
- package/rules/php/js/docs/tooling.md +152 -0
- package/rules/php/lint/docs/lint.md +215 -0
- package/rules/python/docs/fix.md +163 -0
- package/rules/python/js/docs/applies.md +108 -0
- package/rules/python/js/docs/tooling.md +153 -0
- package/rules/python/lint/docs/lint.md +322 -0
- package/rules/rego/docs/fix.md +121 -0
- package/rules/rego/js/docs/applies.md +174 -0
- package/rules/rego/js/docs/lint.md +118 -0
- package/rules/rego/lint/docs/lint.md +204 -0
- package/rules/release/docs/change.md +185 -0
- package/rules/release/docs/fix.md +119 -0
- package/rules/release/docs/release.md +222 -0
- package/rules/release/lib/docs/aggregate.md +246 -0
- package/rules/release/lib/docs/change-file.md +200 -0
- package/rules/release/lib/docs/fallback.md +203 -0
- package/rules/rust/docs/fix.md +129 -0
- package/rules/rust/js/docs/applies.md +140 -0
- package/rules/rust/lib/docs/has-cargo-toml.md +130 -0
- package/rules/security/docs/fix.md +86 -0
- package/rules/security/js/docs/lint.md +171 -0
- package/rules/security/js/docs/sample_secret.md +190 -0
- package/rules/security/js/docs/trufflehog.md +137 -0
- package/rules/security/js/lint.mjs +9 -1
- package/rules/style-lint/docs/fix.md +155 -0
- package/rules/style-lint/js/docs/lint.md +184 -0
- package/rules/style-lint/js/docs/tooling.md +194 -0
- package/rules/tauri/docs/fix.md +158 -0
- package/rules/tauri/js/docs/cargo_mutants_config.md +168 -0
- package/rules/tauri/js/docs/tooling.md +228 -0
- package/rules/test/coverage/coverage.mjs +15 -3
- package/rules/test/docs/fix.md +132 -0
- package/rules/test/js/data/stryker_config/docs/stryker-vue-macros-ignorer.md +138 -0
- package/rules/test/js/data/stryker_config/docs/stryker.config.baseline.md +134 -0
- package/rules/test/js/data/stryker_config/docs/stryker.config.vue.baseline.md +160 -0
- package/rules/test/js/data/vitest_config/docs/vitest.config.baseline.md +195 -0
- package/rules/test/js/docs/cargo_mutants_config.md +173 -0
- package/rules/test/js/docs/location.md +136 -0
- package/rules/test/js/docs/no-process-chdir.md +160 -0
- package/rules/test/js/docs/no-relative-fs-path.md +271 -0
- package/rules/test/js/docs/stryker_config.md +152 -0
- package/rules/test/js/docs/vitest-config-pool-forks.md +174 -0
- package/rules/text/docs/fix.md +118 -0
- package/rules/text/js/docs/forbidden-prettier.md +143 -0
- package/rules/text/js/docs/formatting.md +256 -0
- package/rules/text/js/docs/lint.md +122 -0
- package/rules/text/lint/docs/lint.md +220 -0
- package/rules/text/lint/docs/run-dotenv-linter.md +157 -0
- package/rules/text/lint/docs/run-shellcheck.md +212 -0
- package/rules/text/lint/docs/run-v8r.md +197 -0
- package/rules/vue/docs/fix.md +127 -0
- package/rules/vue/js/docs/packages.md +335 -0
- package/rules/vue/lib/docs/vue-forbidden-imports.md +261 -0
- package/rules/worktree/docs/fix.md +161 -0
- package/schemas/rule-meta.json +5 -1
- package/scripts/auto-rules.mjs +7 -4
- package/scripts/coverage-classify/docs/apply.md +202 -0
- package/scripts/coverage-classify/docs/cache.md +203 -0
- package/scripts/coverage-classify/docs/index.md +218 -0
- package/scripts/coverage-classify/docs/prompt.md +132 -0
- package/scripts/coverage-classify/docs/verdict-schema.md +169 -0
- package/scripts/coverage-fix-extract.mjs +122 -0
- package/scripts/coverage-fix.mjs +1 -1
- package/scripts/dispatcher/docs/graph.md +346 -0
- package/scripts/dispatcher/docs/index.md +236 -0
- package/scripts/dispatcher/docs/trace.md +296 -0
- package/scripts/dispatcher/index.mjs +1 -1
- package/scripts/dispatcher/lib/active.mjs +4 -8
- package/scripts/dispatcher/lib/commands.mjs +7 -11
- package/scripts/dispatcher/lib/docs/active.md +348 -0
- package/scripts/dispatcher/lib/docs/artifact.md +232 -0
- package/scripts/dispatcher/lib/docs/budget.md +167 -0
- package/scripts/dispatcher/lib/docs/capability.md +196 -0
- package/scripts/dispatcher/lib/docs/commands.md +210 -0
- package/scripts/dispatcher/lib/docs/events.md +182 -0
- package/scripts/dispatcher/lib/docs/executor.md +190 -0
- package/scripts/dispatcher/lib/docs/flow-lock.md +161 -0
- package/scripts/dispatcher/lib/docs/flow-resolve.md +267 -0
- package/scripts/dispatcher/lib/docs/gate.md +231 -0
- package/scripts/dispatcher/lib/docs/level.md +335 -0
- package/scripts/dispatcher/lib/docs/plan-panel.md +181 -0
- package/scripts/dispatcher/lib/docs/plan.md +200 -0
- package/scripts/dispatcher/lib/docs/planner.md +269 -0
- package/scripts/dispatcher/lib/docs/review.md +255 -0
- package/scripts/dispatcher/lib/docs/reviewer.md +240 -0
- package/scripts/dispatcher/lib/docs/snapshot.md +247 -0
- package/scripts/dispatcher/lib/docs/spec.md +203 -0
- package/scripts/dispatcher/lib/docs/state-store.md +303 -0
- package/scripts/dispatcher/lib/docs/subagent-runner.md +173 -0
- package/scripts/dispatcher/lib/executor.mjs +6 -1
- package/scripts/dispatcher/lib/flow-resolve.mjs +3 -1
- package/scripts/dispatcher/lib/level.mjs +29 -3
- package/scripts/dispatcher/lib/review.mjs +1 -1
- package/scripts/dispatcher/lib/subagent-runner.mjs +5 -3
- package/scripts/docs/auto-rules.md +376 -0
- package/scripts/docs/auto-skills.md +173 -0
- package/scripts/docs/build-agents-commands.md +183 -0
- package/scripts/docs/cli-entry.md +153 -0
- package/scripts/docs/coverage-fix.md +177 -0
- package/scripts/docs/ensure-nitra-cursor-dev-dependencies.md +189 -0
- package/scripts/lib/changed-files.mjs +4 -1
- package/scripts/lib/docs/changed-files.md +149 -0
- package/scripts/lib/docs/check-mdc-template-refs.md +222 -0
- package/scripts/lib/docs/check-reporter.md +175 -0
- package/scripts/lib/docs/discover-check-rules-from-cursor.md +157 -0
- package/scripts/lib/docs/discover-checkable-rules.md +165 -0
- package/scripts/lib/docs/ensure-tool.md +254 -0
- package/scripts/lib/docs/generated-markdown.md +275 -0
- package/scripts/lib/docs/gha-workflow.md +326 -0
- package/scripts/lib/docs/inline-template-links.md +303 -0
- package/scripts/lib/docs/list-rule-ids.md +156 -0
- package/scripts/lib/docs/load-cursor-config.md +147 -0
- package/scripts/lib/docs/mirror-parity.md +167 -0
- package/scripts/lib/worktree.mjs +26 -0
- package/scripts/worktree-cli.mjs +12 -2
- package/skills/coverage-fix/SKILL.md +34 -45
- package/skills/docgen/SKILL.md +44 -23
- package/skills/docgen/bench/etalon/firebase_hosting.md +19 -0
- package/skills/docgen/bench/etalon/k8s-tree.md +24 -0
- package/skills/docgen/bench/etalon/overlay-paths.md +24 -0
- package/skills/docgen/js/docgen-ignore.mjs +54 -0
- package/skills/docgen/js/docgen-scan.mjs +37 -21
- package/skills/llm-patch/SKILL.md +23 -2
- package/skills/start-check/SKILL.md +26 -53
- package/skills/start-check/js/check.mjs +211 -0
- package/skills/taze/SKILL.md +9 -3
- package/skills/taze/js/diff.mjs +154 -0
- package/types/bin/n-cursor.d.ts +1 -1
- package/skills/fix-tests/SKILL.md +0 -119
- package/skills/fix-tests/meta.json +0 -1
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# tooling.mjs
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Модуль `npm/rules/graphql/js/tooling.mjs` — це **check-скрипт правила `graphql.mdc`**, який перевіряє, що репозиторій налаштований для роботи з GraphQL за наявності у коді tagged template literals `gql\`...\``.
|
|
6
|
+
|
|
7
|
+
Логіка перевірки **умовна**:
|
|
8
|
+
|
|
9
|
+
1. Скрипт рекурсивно обходить дерево проєкту з кореня (`process.cwd()` за замовчуванням) і збирає файли-кандидати (`.vue`, `.js`, `.ts`, `.jsx`, `.tsx` тощо) — пропускаючи службові артефакти типу `.d.ts`, `auto-imports.d.ts` тощо.
|
|
10
|
+
2. Для кожного кандидата виконує AST-сканування (oxc-parser; для `.vue` — після витягування блоку `<script>`) у пошуках `gql` tagged template literal.
|
|
11
|
+
3. Якщо **жодного збігу не знайдено** — перевірка завершується успішно й нічого більше не вимагає.
|
|
12
|
+
4. Якщо `gql\`…\`` **знайдено хоча б в одному файлі** — модуль вимагає:
|
|
13
|
+
- наявність файлу `.graphqlrc.yml` у корені репозиторію (GraphQL Config);
|
|
14
|
+
- відповідність `.vscode/extensions.json` rego-пакету `graphql.vscode_extensions` (тобто рекомендацію розширення VS Code `graphql.vscode-graphql`).
|
|
15
|
+
|
|
16
|
+
Модуль є частиною інфраструктури `n-cursor` для перевірок правил `.mdc` і дотримується контракту check-скрипта: повертає exit code `0` при успіху й `1` при порушенні.
|
|
17
|
+
|
|
18
|
+
## Експорти / API
|
|
19
|
+
|
|
20
|
+
| Експорт | Тип | Призначення |
|
|
21
|
+
| ----------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------- |
|
|
22
|
+
| `GRAPHQL_RC_FILENAME` | `string` (const, `.graphqlrc.yml`) | Очікувана назва файлу GraphQL Config у корені проєкту (з `graphql.mdc`). |
|
|
23
|
+
| `REQUIRED_GRAPHQL_VSCODE_EXTENSION` | `string` (const, `graphql.vscode-graphql`) | Ідентифікатор обов'язкового розширення VS Code, яке має бути в `recommendations`. |
|
|
24
|
+
| `check(cwd?)` | `async function` | Основна точка входу — виконує всю перевірку правила `graphql.mdc` і повертає exit code. |
|
|
25
|
+
|
|
26
|
+
Внутрішні (не експортовані) хелпери:
|
|
27
|
+
|
|
28
|
+
- `collectScanCandidates(root, ignorePaths)` — збір абсолютних шляхів файлів для сканування.
|
|
29
|
+
- `collectGqlHits(root, candidates)` — фільтрація кандидатів за наявністю `gql` tagged template.
|
|
30
|
+
- `checkExtensionsRecommendation(pass, fail, cwd)` — делегування перевірки `.vscode/extensions.json` rego-пакету `graphql.vscode_extensions` через `conftest`.
|
|
31
|
+
|
|
32
|
+
## Функції
|
|
33
|
+
|
|
34
|
+
### `collectScanCandidates(root, ignorePaths)`
|
|
35
|
+
|
|
36
|
+
**Сигнатура:**
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
async function collectScanCandidates(root: string, ignorePaths: string[]): Promise<string[]>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Параметри:**
|
|
43
|
+
|
|
44
|
+
- `root` — абсолютний шлях до кореня репозиторію, з якого починається обхід.
|
|
45
|
+
- `ignorePaths` — масив абсолютних шляхів каталогів, які повністю виключаються з обходу (формується через `loadCursorIgnorePaths`).
|
|
46
|
+
|
|
47
|
+
**Що робить:**
|
|
48
|
+
|
|
49
|
+
- Викликає `walkDir(root, visitor, ignorePaths)` (рекурсивний обхід файлової системи).
|
|
50
|
+
- Для кожного відвіданого файлу обчислює відносний шлях від `root`, нормалізує роздільники (Windows `\` → POSIX `/`).
|
|
51
|
+
- Застосовує два фільтри:
|
|
52
|
+
- `shouldSkipFileForGqlScan(rel)` — пропуск службових файлів (`.d.ts`, `auto-imports.d.ts` тощо).
|
|
53
|
+
- `isGqlScanSourceFile(rel)` — допуск лише відповідних розширень (Vue/JS/TS-сімейство).
|
|
54
|
+
- Накопичує **абсолютні** шляхи прийнятих файлів у масив `candidates`.
|
|
55
|
+
|
|
56
|
+
**Повертає:** `Promise<string[]>` — список абсолютних шляхів файлів-кандидатів.
|
|
57
|
+
|
|
58
|
+
**Side effects:** читає метадані файлової системи (через `walkDir`); записів не робить.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
### `collectGqlHits(root, candidates)`
|
|
63
|
+
|
|
64
|
+
**Сигнатура:**
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
async function collectGqlHits(root: string, candidates: string[]): Promise<string[]>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Параметри:**
|
|
71
|
+
|
|
72
|
+
- `root` — абсолютний шлях до кореня (для обчислення відносних шляхів у результаті).
|
|
73
|
+
- `candidates` — список абсолютних шляхів файлів, отриманих від `collectScanCandidates`.
|
|
74
|
+
|
|
75
|
+
**Що робить:**
|
|
76
|
+
|
|
77
|
+
- Послідовно (`for-of` + `await`) для кожного абсолютного шляху:
|
|
78
|
+
- обчислює відносний шлях і нормалізує роздільники до `/`;
|
|
79
|
+
- читає вміст файлу через `readFile(absPath, 'utf8')`;
|
|
80
|
+
- викликає `sourceFileHasGqlTaggedTemplate(content, rel)` — парсер AST oxc, що враховує особливості `.vue` (витягування `<script>`);
|
|
81
|
+
- якщо результат `true` — додає **відносний** шлях до результуючого масиву `hits`.
|
|
82
|
+
|
|
83
|
+
**Повертає:** `Promise<string[]>` — відносні шляхи файлів, у яких знайдено хоча б одне `gql` tagged template.
|
|
84
|
+
|
|
85
|
+
**Side effects:** читання вмісту файлів з диска. Запис відсутній.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
### `checkExtensionsRecommendation(pass, fail, cwd)`
|
|
90
|
+
|
|
91
|
+
**Сигнатура:**
|
|
92
|
+
|
|
93
|
+
```js
|
|
94
|
+
function checkExtensionsRecommendation(
|
|
95
|
+
pass: (msg: string) => void,
|
|
96
|
+
fail: (msg: string) => void,
|
|
97
|
+
cwd: string
|
|
98
|
+
): void
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Параметри:**
|
|
102
|
+
|
|
103
|
+
- `pass` — функція-репортер «успішно» (отримана з `createCheckReporter()`).
|
|
104
|
+
- `fail` — функція-репортер «порушення».
|
|
105
|
+
- `cwd` — абсолютний корінь репозиторію.
|
|
106
|
+
|
|
107
|
+
**Що робить:**
|
|
108
|
+
|
|
109
|
+
1. Формує відносний (`.vscode/extensions.json`) і абсолютний шляхи до файлу VS Code-конфігу.
|
|
110
|
+
2. Якщо файл **не існує** (`existsSync`) — викликає `fail(...)` з повідомленням про те, що треба створити файл і додати `graphql.vscode-graphql` у `recommendations`, посилаючись на `graphql.mdc`. Виходить.
|
|
111
|
+
3. Інакше викликає `runConftestBatch({ policyDirRel: 'graphql/vscode_extensions', namespace: 'graphql.vscode_extensions', files: [pathAbs] })` — делегує перевірку rego-пакету conftest-у.
|
|
112
|
+
4. Якщо `violations.length === 0` — викликає `pass(...)` з повідомленням про відповідність. Інакше — для кожного порушення `v` викликає `fail(v.message)`.
|
|
113
|
+
|
|
114
|
+
**Повертає:** нічого (`void`). Результати фіксуються через `pass` / `fail`.
|
|
115
|
+
|
|
116
|
+
**Side effects:**
|
|
117
|
+
|
|
118
|
+
- читання `.vscode/extensions.json` через зовнішній conftest-процес;
|
|
119
|
+
- виклик `conftest` (binary) усередині `runConftestBatch`;
|
|
120
|
+
- зміна стану внутрішнього reporter-у (накопичення успіхів/порушень).
|
|
121
|
+
|
|
122
|
+
**Виклик умовний:** виконується лише після того, як основна функція `check` виявила `gql` у дереві.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
### `check(cwd = process.cwd())`
|
|
127
|
+
|
|
128
|
+
**Сигнатура:**
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
export async function check(cwd?: string): Promise<number>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Параметри:**
|
|
135
|
+
|
|
136
|
+
- `cwd` — _необов'язковий_ абсолютний шлях до кореня репозиторію. За замовчуванням `process.cwd()`.
|
|
137
|
+
|
|
138
|
+
**Що робить (потік):**
|
|
139
|
+
|
|
140
|
+
1. Створює репортер `createCheckReporter()` і деструктурує з нього `pass`, `fail`.
|
|
141
|
+
2. Завантажує список ігнорованих шляхів через `loadCursorIgnorePaths(root)`.
|
|
142
|
+
3. Викликає `collectScanCandidates(root, ignorePaths)` — отримує список файлів-кандидатів.
|
|
143
|
+
4. Викликає `collectGqlHits(root, candidates)` — отримує файли з `gql`.
|
|
144
|
+
5. **Розгалуження:**
|
|
145
|
+
- Якщо `hits.length === 0` — викликає `pass(...)` з повідомленням «немає `gql\`…\``у джерелах, переглянуто N файлів —`.graphqlrc.yml`не вимагається» і **повертає**`reporter.getExitCode()`(зазвичай`0`).
|
|
146
|
+
- Інакше викликає `pass(...)` зі звітом про N файлів (з перших 5 з суфіксом `…` якщо більше).
|
|
147
|
+
6. Перевіряє наявність `GRAPHQL_RC_FILENAME` (`.graphqlrc.yml`) у корені через `existsSync`:
|
|
148
|
+
- Існує → `pass('.graphqlrc.yml існує')`.
|
|
149
|
+
- Не існує → `fail(...)` з вимогою додати GraphQL Config (з посиланням на `graphql.mdc`).
|
|
150
|
+
7. Викликає `checkExtensionsRecommendation(pass, fail, root)` для перевірки `.vscode/extensions.json`.
|
|
151
|
+
8. Повертає `reporter.getExitCode()`: `0` — усі перевірки пройдені, `1` — є хоча б одне порушення.
|
|
152
|
+
|
|
153
|
+
**Повертає:** `Promise<number>` — exit code (`0` — OK, `1` — порушення).
|
|
154
|
+
|
|
155
|
+
**Side effects:**
|
|
156
|
+
|
|
157
|
+
- читання файлової системи (метадані + вміст файлів кандидатів);
|
|
158
|
+
- читання `.cursor/...` ignore-конфігу;
|
|
159
|
+
- читання `.vscode/extensions.json` (опосередковано через conftest);
|
|
160
|
+
- запуск процесу `conftest` через `runConftestBatch`;
|
|
161
|
+
- мутація стану внутрішнього reporter-у (накопичення pass/fail-повідомлень).
|
|
162
|
+
|
|
163
|
+
## Залежності
|
|
164
|
+
|
|
165
|
+
### Node.js builtins
|
|
166
|
+
|
|
167
|
+
| Модуль | Що використовується | Призначення |
|
|
168
|
+
| ------------------ | ------------------- | ---------------------------------------------------------------------------- |
|
|
169
|
+
| `node:fs` | `existsSync` | Синхронна перевірка наявності `.graphqlrc.yml` та `.vscode/extensions.json`. |
|
|
170
|
+
| `node:fs/promises` | `readFile` | Асинхронне читання вмісту файлів-кандидатів. |
|
|
171
|
+
| `node:path` | `join`, `relative` | Збирання абсолютних шляхів і обчислення відносних шляхів від кореня. |
|
|
172
|
+
|
|
173
|
+
### Внутрішні модулі репозиторію
|
|
174
|
+
|
|
175
|
+
- `../../../scripts/lib/check-reporter.mjs` — `createCheckReporter`: фабрика репортера зі стандартним інтерфейсом `pass` / `fail` / `getExitCode()`.
|
|
176
|
+
- `../lib/graphql-gql-scan.mjs`:
|
|
177
|
+
- `isGqlScanSourceFile(rel)` — предикат «це source-файл, який потенційно містить `gql`» (за розширенням);
|
|
178
|
+
- `shouldSkipFileForGqlScan(rel)` — предикат «цей файл слід ігнорувати при скануванні» (`.d.ts`, `auto-imports.d.ts` тощо);
|
|
179
|
+
- `sourceFileHasGqlTaggedTemplate(content, rel)` — AST-перевірка на наявність `gql\`...\``(через oxc-parser; для`.vue`— після витягування`<script>`).
|
|
180
|
+
- `../../../scripts/lib/load-cursor-config.mjs` — `loadCursorIgnorePaths(root)`: повертає абсолютні шляхи каталогів, повністю виключених з обходу (узгоджено з іншими check-скриптами).
|
|
181
|
+
- `../../../scripts/lib/run-conftest-batch.mjs` — `runConftestBatch({ policyDirRel, namespace, files })`: запускає `conftest` з rego-пакетом і повертає масив `violations` (об'єкти з `.message`).
|
|
182
|
+
- `../../../scripts/utils/walkDir.mjs` — `walkDir(root, visitor, ignorePaths)`: рекурсивний обхід файлової системи з підтримкою списку ігнорування.
|
|
183
|
+
|
|
184
|
+
### Зовнішні артефакти (не імпортуються, але потрібні в runtime)
|
|
185
|
+
|
|
186
|
+
- **`conftest`** як CLI-binary (запускається з `runConftestBatch`).
|
|
187
|
+
- **Rego-політика** в `graphql/vscode_extensions` з namespace `graphql.vscode_extensions` (перевіряє `.vscode/extensions.json`).
|
|
188
|
+
- **`.cursor/ignore`-конфіг** у корені (читається `loadCursorIgnorePaths`).
|
|
189
|
+
- **`graphql.mdc`** — правило, яке цей check реалізує.
|
|
190
|
+
|
|
191
|
+
## Потік виконання / Використання
|
|
192
|
+
|
|
193
|
+
### Запуск як check
|
|
194
|
+
|
|
195
|
+
Модуль використовується з єдиною експортованою функцією `check`:
|
|
196
|
+
|
|
197
|
+
```js
|
|
198
|
+
import { check } from 'npm/rules/graphql/js/tooling.mjs'
|
|
199
|
+
|
|
200
|
+
const exitCode = await check() // або await check('/absolute/path/to/repo')
|
|
201
|
+
process.exit(exitCode)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Типово викликається диспетчером check-скриптів інфраструктури `n-cursor` (наприклад, із `npm/scripts/...`), який отримує exit code й агрегує результати по всіх правилах `.mdc`.
|
|
205
|
+
|
|
206
|
+
### Послідовність дій усередині `check`
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
process.cwd() (або переданий cwd)
|
|
210
|
+
│
|
|
211
|
+
▼
|
|
212
|
+
createCheckReporter() ──► { pass, fail, getExitCode }
|
|
213
|
+
│
|
|
214
|
+
▼
|
|
215
|
+
loadCursorIgnorePaths(root) ──► ignorePaths
|
|
216
|
+
│
|
|
217
|
+
▼
|
|
218
|
+
collectScanCandidates(root, ignorePaths)
|
|
219
|
+
│ walkDir(root, visitor, ignorePaths)
|
|
220
|
+
│ filter: !shouldSkipFileForGqlScan && isGqlScanSourceFile
|
|
221
|
+
▼
|
|
222
|
+
candidates: string[]
|
|
223
|
+
│
|
|
224
|
+
▼
|
|
225
|
+
collectGqlHits(root, candidates)
|
|
226
|
+
│ for each: readFile + sourceFileHasGqlTaggedTemplate (oxc-parser AST)
|
|
227
|
+
▼
|
|
228
|
+
hits: string[]
|
|
229
|
+
│
|
|
230
|
+
├── hits.length === 0 ──► pass("немає gql у джерелах") ──► return 0
|
|
231
|
+
│
|
|
232
|
+
└── hits.length > 0
|
|
233
|
+
│
|
|
234
|
+
├── pass("Знайдено gql у N файлі(ах): ...")
|
|
235
|
+
│
|
|
236
|
+
├── existsSync(.graphqlrc.yml)
|
|
237
|
+
│ ├── true ──► pass(".graphqlrc.yml існує")
|
|
238
|
+
│ └── false ──► fail("Відсутній .graphqlrc.yml ...")
|
|
239
|
+
│
|
|
240
|
+
├── checkExtensionsRecommendation(pass, fail, root)
|
|
241
|
+
│ ├── !existsSync(.vscode/extensions.json) ──► fail(...)
|
|
242
|
+
│ └── runConftestBatch(graphql/vscode_extensions)
|
|
243
|
+
│ ├── violations.length === 0 ──► pass(...)
|
|
244
|
+
│ └── for v of violations: fail(v.message)
|
|
245
|
+
│
|
|
246
|
+
└── return reporter.getExitCode() (0 або 1)
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Семантика повідомлень
|
|
250
|
+
|
|
251
|
+
- `pass` — інформативне повідомлення про успішну перевірку (логнеться як OK, не впливає на exit code).
|
|
252
|
+
- `fail` — повідомлення про порушення; навіть одне `fail` робить `getExitCode()` рівним `1`.
|
|
253
|
+
|
|
254
|
+
### Чому перевірка умовна
|
|
255
|
+
|
|
256
|
+
`graphql.mdc` вимагає `.graphqlrc.yml` і VS Code-розширення `graphql.vscode-graphql` **лише** для проєктів, де реально використовуються `gql` tagged template literals. У монорепо це дозволяє не «спамити» правилом workspace-и, що не торкаються GraphQL — check спочатку доводить релевантність (`hits.length > 0`), і лише потім вимагає інфраструктуру.
|
|
257
|
+
|
|
258
|
+
### Обмеження та особливості
|
|
259
|
+
|
|
260
|
+
- Шляхи в `hits` та в попередженнях завжди **відносні** до `root`, з POSIX-роздільниками `/` (навіть на Windows).
|
|
261
|
+
- Перші 5 файлів-збігів показуються у повідомленні `pass`; решта приховується за суфіксом `…`.
|
|
262
|
+
- Якщо `gql` знайдено, але `.vscode/extensions.json` відсутній — це порушення, незалежне від rego-перевірки (тобто `fail` без виклику conftest).
|
|
263
|
+
- `collectGqlHits` читає файли **послідовно** (без `Promise.all`) — це навмисно, щоб не перевантажувати I/O при великих репозиторіях.
|
|
264
|
+
- Виявлення `gql` — на рівні AST (oxc-parser), а не регулярних виразів; рядкові збіги типу `// gql\`...\`` у коментарі не дають false-positive.
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# graphql-gql-scan.mjs
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Модуль `graphql-gql-scan.mjs` — це сканер сирцевих файлів проєкту, який визначає, чи містить файл tagged template літерал із тегом `gql` (тобто конструкцію виду `` gql`…` ``). Сканер призначений для роботи разом із правилом `graphql.mdc` і використовується інфраструктурою лінту/правил для виявлення місць, де описано GraphQL-запити у форматі вкладених шаблонних рядків.
|
|
6
|
+
|
|
7
|
+
Ключові властивості:
|
|
8
|
+
|
|
9
|
+
- **Підтримує** файли з розширеннями `.vue`, `.js`, `.mjs`, `.cjs`, `.jsx`, `.ts`, `.mts`, `.cts`, `.tsx`.
|
|
10
|
+
- Для **`.vue`** (Single File Component) бере **лише** вміст блоків `<script>` / `<script setup>`, ігноруючи `<template>` і `<style>`.
|
|
11
|
+
- Для парсингу використовує **`oxc-parser`** (`parseSync`) — отримує повний AST і виконує **рекурсивний обхід**, шукаючи вузли `TaggedTemplateExpression` із тегом-`Identifier` з ім'ям `gql`.
|
|
12
|
+
- Дозволяє пропускати типові generated-файли (`*.d.ts`, `auto-imports.d.ts`, `components.d.ts`).
|
|
13
|
+
- Реалізований як **самодостатній модуль**: не імпортує функцій з інших правил (наприклад, із паралельного `vue-forbidden-imports.mjs` — там екстрактор `<script>` реалізовано окремо), щоб уникати cross-rule імпортів.
|
|
14
|
+
|
|
15
|
+
Файл експортує три чисті функції: одна виконує перевірку наявності `gql`-тега, дві допоміжні класифікують шлях файлу (чи варто сканувати, чи варто пропустити).
|
|
16
|
+
|
|
17
|
+
## Експорти / API
|
|
18
|
+
|
|
19
|
+
Файл є ES-модулем (`.mjs`). Має такі іменовані експорти:
|
|
20
|
+
|
|
21
|
+
| Експорт | Тип | Призначення |
|
|
22
|
+
| -------------------------------- | ------------------------------------ | ------------------------------------------------------------------------- |
|
|
23
|
+
| `sourceFileHasGqlTaggedTemplate` | `(content, relativePath) => boolean` | Парсить вміст і повертає `true`, якщо знайдено `` gql`…` ``. |
|
|
24
|
+
| `isGqlScanSourceFile` | `(relativePath) => boolean` | `true` якщо файл за розширенням підлягає скануванню. |
|
|
25
|
+
| `shouldSkipFileForGqlScan` | `(relativePosix) => boolean` | `true` якщо файл — типовий generated/declaration і його треба пропустити. |
|
|
26
|
+
|
|
27
|
+
Default-експорту **немає**. Усі допоміжні функції (`extractVueScriptBlocks`, `contentForGqlScan`, `langFromPath`, `virtualPathForParse`, `astContainsGqlTag`) — приватні (module-scope), назовні не експонуються.
|
|
28
|
+
|
|
29
|
+
## Функції
|
|
30
|
+
|
|
31
|
+
### `extractVueScriptBlocks(sfc)` _(приватна)_
|
|
32
|
+
|
|
33
|
+
- **Сигнатура**: `(sfc: string) => string`
|
|
34
|
+
- **Параметри**:
|
|
35
|
+
- `sfc` — сирий вміст `.vue` файлу як рядок.
|
|
36
|
+
- **Повертає**: рядок-конкатенацію вмісту **усіх** блоків `<script>` (включно з `<script setup>`), з'єднаних подвійним `\n\n`. Якщо `<script>` блоків немає — повертає порожній рядок.
|
|
37
|
+
- **Side effects**: жодних мутацій зовнішнього стану; перед роботою примусово скидає `VUE_SCRIPT_BLOCK_RE.lastIndex = 0` (модуль-локальний `RegExp` з прапором `g` має stateful `lastIndex`), щоб попередні виклики не вплинули на новий парсинг.
|
|
38
|
+
- **Алгоритм**: цикл `RegExp.exec` по глобальному регулярному виразу `VUE_SCRIPT_BLOCK_RE` — `/<script\b[^>]*>([\s\S]*?)<\/script>/gi`. Перша захопна група містить тіло блоку без обгортки.
|
|
39
|
+
|
|
40
|
+
### `contentForGqlScan(content, filePath)` _(приватна)_
|
|
41
|
+
|
|
42
|
+
- **Сигнатура**: `(content: string, filePath: string) => string`
|
|
43
|
+
- **Параметри**:
|
|
44
|
+
- `content` — сирий вміст файлу.
|
|
45
|
+
- `filePath` — відносний шлях; використовується **лише** для перевірки розширення.
|
|
46
|
+
- **Повертає**: якщо `filePath` закінчується на `.vue`, повертає результат `extractVueScriptBlocks(content)`; інакше — без змін `content`.
|
|
47
|
+
- **Side effects**: немає.
|
|
48
|
+
|
|
49
|
+
### `langFromPath(filePath)` _(приватна)_
|
|
50
|
+
|
|
51
|
+
- **Сигнатура**: `(filePath: string) => 'js' | 'jsx' | 'ts' | 'tsx'`
|
|
52
|
+
- **Параметри**:
|
|
53
|
+
- `filePath` — реальний або віртуальний шлях.
|
|
54
|
+
- **Повертає** мову для `parseSync` із `oxc-parser` за пріоритетом перевірок (на lowercased рядку):
|
|
55
|
+
1. `.tsx` → `'tsx'`
|
|
56
|
+
2. `.ts` / `.mts` / `.cts` → `'ts'`
|
|
57
|
+
3. `.jsx` → `'jsx'`
|
|
58
|
+
4. усе інше → `'js'`
|
|
59
|
+
- **Side effects**: немає.
|
|
60
|
+
|
|
61
|
+
### `virtualPathForParse(relativePath)` _(приватна)_
|
|
62
|
+
|
|
63
|
+
- **Сигнатура**: `(relativePath: string) => string`
|
|
64
|
+
- **Параметри**:
|
|
65
|
+
- `relativePath` — відносний шлях до сирцевого файлу.
|
|
66
|
+
- **Повертає**: для `.vue` — той самий шлях із розширенням, заміненим на `.ts` (через `VUE_EXTENSION_RE = /\.vue$/u`). Для всіх інших — повертає `relativePath` як є. Зроблено, щоб `oxc-parser` парсив SFC як TypeScript-код (бо `<script setup>` найчастіше TS).
|
|
67
|
+
- **Side effects**: немає.
|
|
68
|
+
|
|
69
|
+
### `astContainsGqlTag(node)` _(приватна)_
|
|
70
|
+
|
|
71
|
+
- **Сигнатура**: `(node: unknown) => boolean`
|
|
72
|
+
- **Параметри**:
|
|
73
|
+
- `node` — будь-який вузол AST або корінь `program`, отриманий від `parseSync`.
|
|
74
|
+
- **Повертає**: `true`, якщо в дереві (поточному або будь-якому нащадку) знайдено вузол `TaggedTemplateExpression`, у якого `tag.type === 'Identifier'` **та** `tag.name === 'gql'`. Інакше — `false`.
|
|
75
|
+
- **Алгоритм** (рекурсивний DFS):
|
|
76
|
+
1. Якщо `node` — `null`/`undefined` → `false`.
|
|
77
|
+
2. Якщо `typeof node !== 'object'` (примітив) → `false`.
|
|
78
|
+
3. Якщо масив — `Array.prototype.some` з рекурсивним викликом.
|
|
79
|
+
4. Якщо `node.type === 'TaggedTemplateExpression'` і його `tag` — `Identifier` з ім'ям `gql` → `true`.
|
|
80
|
+
5. Інакше — ітерація по `Object.keys(node)`, **пропускаючи** ключі `loc` та `range` (це позиційна метаінформація без AST-вузлів) і рекурсивно перевіряючи кожне значення.
|
|
81
|
+
- **Side effects**: немає; функція чиста.
|
|
82
|
+
|
|
83
|
+
### `sourceFileHasGqlTaggedTemplate(content, relativePath)` _(експорт)_
|
|
84
|
+
|
|
85
|
+
- **Сигнатура**: `(content: string, relativePath: string) => boolean`
|
|
86
|
+
- **Параметри**:
|
|
87
|
+
- `content` — сирий вміст файлу.
|
|
88
|
+
- `relativePath` — відносний posix-шлях (використовується для вибору режиму парсингу й мови).
|
|
89
|
+
- **Повертає**: `true`, якщо у файлі знайдено `gql`-tagged template; `false` — в усіх інших випадках, включно з помилками парсингу та винятками.
|
|
90
|
+
- **Алгоритм**:
|
|
91
|
+
1. Отримати текст для сканування: `contentForGqlScan(content, relativePath)`.
|
|
92
|
+
2. Обчислити шлях для парсера: `virtualPathForParse(relativePath)` (для SFC — `.ts`).
|
|
93
|
+
3. Визначити мову: `langFromPath(pathForLang)`.
|
|
94
|
+
4. Викликати `parseSync(pathForLang, scan, { lang, sourceType: 'module' })` всередині `try…catch`.
|
|
95
|
+
5. Якщо `result.errors?.length` істинне (парсер повернув помилки, але не кинув виняток) — повернути `false`.
|
|
96
|
+
6. Інакше — `astContainsGqlTag(result.program)`.
|
|
97
|
+
7. Будь-який `throw` від `parseSync` ловиться `catch {}` і повертається `false`.
|
|
98
|
+
- **Side effects**:
|
|
99
|
+
- Викликає синхронний `parseSync` з `oxc-parser` — CPU-bound операція над текстом.
|
|
100
|
+
- **Не** мутує переданих аргументів і не модифікує жодного зовнішнього стану.
|
|
101
|
+
- Помилки парсера не пробкидаються нагору — інтерпретація: «якщо не парситься, то й `gql` ми достеменно не знаємо, тому не сигналізуємо».
|
|
102
|
+
|
|
103
|
+
### `isGqlScanSourceFile(relativePath)` _(експорт)_
|
|
104
|
+
|
|
105
|
+
- **Сигнатура**: `(relativePath: string) => boolean`
|
|
106
|
+
- **Параметри**:
|
|
107
|
+
- `relativePath` — відносний шлях.
|
|
108
|
+
- **Повертає**: `true`, якщо шлях відповідає `SOURCE_FILE_RE = /\.(vue|[cm]?[jt]sx?)$/u`, тобто закінчується на одне з: `.vue`, `.js`, `.mjs`, `.cjs`, `.jsx`, `.ts`, `.mts`, `.cts`, `.tsx`. Інакше — `false`.
|
|
109
|
+
- **Side effects**: немає.
|
|
110
|
+
|
|
111
|
+
### `shouldSkipFileForGqlScan(relativePosix)` _(експорт)_
|
|
112
|
+
|
|
113
|
+
- **Сигнатура**: `(relativePosix: string) => boolean`
|
|
114
|
+
- **Параметри**:
|
|
115
|
+
- `relativePosix` — шлях у форматі posix (`/` як роздільник).
|
|
116
|
+
- **Повертає**: `true`, якщо файл — typical generated/declaration і його не варто сканувати:
|
|
117
|
+
- Базове ім'я (останній сегмент після `/`) дорівнює `auto-imports.d.ts` **або** `components.d.ts`; **або**
|
|
118
|
+
- Шлях закінчується на `.d.ts`.
|
|
119
|
+
- В інших випадках — `false`.
|
|
120
|
+
- **Side effects**: немає.
|
|
121
|
+
|
|
122
|
+
### Огляд приватних констант
|
|
123
|
+
|
|
124
|
+
| Константа | Значення | Призначення |
|
|
125
|
+
| --------------------- | ----------------------------------------- | ----------------------------------------------------------------- | -------------------------------------------------------- |
|
|
126
|
+
| `VUE_EXTENSION_RE` | `/\.vue$/u` | Перевірка/заміна розширення `.vue` на `.ts` у віртуальному шляху. |
|
|
127
|
+
| `SOURCE_FILE_RE` | `/\.(vue | [cm]?[jt]sx?)$/u` | Класифікатор «це сирцевий файл, що підлягає скануванню». |
|
|
128
|
+
| `VUE_SCRIPT_BLOCK_RE` | `/<script\b[^>]*>([\s\S]*?)<\/script>/gi` | Глобальний матч блоків `<script>` у `.vue` файлі. |
|
|
129
|
+
|
|
130
|
+
## Залежності
|
|
131
|
+
|
|
132
|
+
### Зовнішні (`import`)
|
|
133
|
+
|
|
134
|
+
- **`oxc-parser`** — JavaScript-парсер на Rust (через native-bindings); експортує `parseSync(filePath, sourceCode, options)`. У цьому модулі використовується для побудови AST і визначає семантику `gql` як **tag identifier** (а не звичайного виклику функції чи стрічкового збігу).
|
|
135
|
+
- Виклик: `parseSync(pathForLang, scan, { lang, sourceType: 'module' })`.
|
|
136
|
+
- Очікувана відповідь — об'єкт `{ program, errors? }`, де `program` — корінь ESTree-сумісного AST.
|
|
137
|
+
|
|
138
|
+
### Внутрішні
|
|
139
|
+
|
|
140
|
+
- Цей модуль **не** імпортує жодних інших модулів проєкту. Це усвідомлене рішення: правила в `npm/rules/<rule>/` тримаються самодостатніми, щоб уникати cross-rule імпортів. Аналогічний (паралельний) екстрактор `<script>` блоків існує в `rules/vue/js/packages/vue-forbidden-imports.mjs` — модулі не діляться кодом.
|
|
141
|
+
|
|
142
|
+
### Зовнішні споживачі (де модуль використовується)
|
|
143
|
+
|
|
144
|
+
Експортовані функції призначені для виклику з шарів інтеграції правила `graphql.mdc` (наприклад, скриптів-перевірок `check-*.mjs` у правилі `graphql`). Конкретний caller у цьому файлі не описаний — він зовнішній відносно модуля.
|
|
145
|
+
|
|
146
|
+
## Потік виконання / Використання
|
|
147
|
+
|
|
148
|
+
### Типовий потік для одного файлу
|
|
149
|
+
|
|
150
|
+
1. Caller обходить файли проєкту (наприклад, через `git ls-files`).
|
|
151
|
+
2. Для кожного шляху викликає `isGqlScanSourceFile(relativePath)`. Якщо `false` — пропустити.
|
|
152
|
+
3. Викликає `shouldSkipFileForGqlScan(relativePosix)`. Якщо `true` (наприклад, це `*.d.ts` або `auto-imports.d.ts`/`components.d.ts`) — пропустити.
|
|
153
|
+
4. Читає вміст файлу з диска (як рядок UTF-8) — це робить caller, не цей модуль.
|
|
154
|
+
5. Викликає `sourceFileHasGqlTaggedTemplate(content, relativePath)`:
|
|
155
|
+
- Для `.vue` — вирізаються блоки `<script>`/`<script setup>`, склеюються через `\n\n`, парсяться як TypeScript.
|
|
156
|
+
- Для решти — парситься увесь вміст з мовою, виведеною з розширення.
|
|
157
|
+
- Помилки парсера (як у `result.errors`, так і у вигляді винятків) тихо повертають `false`.
|
|
158
|
+
6. Якщо повернулось `true` — caller знає, що у файлі є `` gql`…` ``-літерал, і може застосувати свою логіку правила (наприклад, обов'язково винести запит у `.gql`/`.graphql` файл, або навпаки — це очікувано і допустимо).
|
|
159
|
+
|
|
160
|
+
### Приклад (псевдокод використання)
|
|
161
|
+
|
|
162
|
+
```js
|
|
163
|
+
import { isGqlScanSourceFile, shouldSkipFileForGqlScan, sourceFileHasGqlTaggedTemplate } from './graphql-gql-scan.mjs'
|
|
164
|
+
import { readFile } from 'node:fs/promises'
|
|
165
|
+
|
|
166
|
+
async function findFilesWithGqlTag(relativePaths) {
|
|
167
|
+
const hits = []
|
|
168
|
+
for (const rel of relativePaths) {
|
|
169
|
+
if (!isGqlScanSourceFile(rel)) continue
|
|
170
|
+
if (shouldSkipFileForGqlScan(rel)) continue
|
|
171
|
+
const content = await readFile(rel, 'utf8')
|
|
172
|
+
if (sourceFileHasGqlTaggedTemplate(content, rel)) {
|
|
173
|
+
hits.push(rel)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return hits
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Семантичні гарантії
|
|
181
|
+
|
|
182
|
+
- **Тільки `Identifier` з ім'ям `gql`**: вираз виду `gql.foo\`…\``(MemberExpression як tag) **не** буде матчитися, тому що перевіряється`tag.type === 'Identifier'`. Це навмисно — правило ловить канонічну форму `` gql`…` ``.
|
|
183
|
+
- **Імпорт `gql`**: модуль не аналізує імпорти й не вимагає, щоб ідентифікатор `gql` був реально визначений у файлі — достатньо, що він використаний як тег. Перевірка походження тега — це справа окремих правил/перевірок (наприклад, `@apollo/client` vs `graphql-tag`).
|
|
184
|
+
- **`.vue` без `<script>`**: SFC без скриптових блоків матиме порожній вхід для парсера → `parseSync` поверне валідний порожній AST → `astContainsGqlTag` → `false`.
|
|
185
|
+
- **Невалідний код**: повертає `false` (як helpers `result.errors?.length`, так і try/catch). Це консервативна поведінка: краще пропустити, ніж дати false-positive.
|
|
186
|
+
|
|
187
|
+
## Rebuild Test
|
|
188
|
+
|
|
189
|
+
Цю секцію можна використати як «специфікацію»: за наведеним нижче переліком сигнатур, констант та поведінкових тверджень файл `graphql-gql-scan.mjs` можна перевідтворити з нуля.
|
|
190
|
+
|
|
191
|
+
**Імпорти**:
|
|
192
|
+
|
|
193
|
+
- Іменований імпорт `parseSync` з `'oxc-parser'`.
|
|
194
|
+
|
|
195
|
+
**Module-scope константи**:
|
|
196
|
+
|
|
197
|
+
- `VUE_EXTENSION_RE = /\.vue$/u`
|
|
198
|
+
- `SOURCE_FILE_RE = /\.(vue|[cm]?[jt]sx?)$/u`
|
|
199
|
+
- `VUE_SCRIPT_BLOCK_RE = /<script\b[^>]*>([\s\S]*?)<\/script>/gi`
|
|
200
|
+
|
|
201
|
+
**Функції (порядок як у файлі)**:
|
|
202
|
+
|
|
203
|
+
1. `function extractVueScriptBlocks(sfc)` — приватна. Скидає `VUE_SCRIPT_BLOCK_RE.lastIndex = 0`; у циклі `while` викликає `VUE_SCRIPT_BLOCK_RE.exec(sfc)`; складає захоплення `m[1]` у масив `chunks`; повертає `chunks.join('\n\n')`.
|
|
204
|
+
2. `function contentForGqlScan(content, filePath)` — приватна. Якщо `filePath.endsWith('.vue')` → `extractVueScriptBlocks(content)`; інакше → `content`.
|
|
205
|
+
3. `function langFromPath(filePath)` — приватна. `lower = filePath.toLowerCase()`; повертає `'tsx' | 'ts' | 'jsx' | 'js'` за пріоритетом `.tsx`, `.ts|.mts|.cts`, `.jsx`, інакше `js`.
|
|
206
|
+
4. `function virtualPathForParse(relativePath)` — приватна. Якщо `.vue` → `replace(VUE_EXTENSION_RE, '.ts')`; інакше — без змін.
|
|
207
|
+
5. `function astContainsGqlTag(node)` — приватна, рекурсивна. Послідовність перевірок: `null`/`undefined` → `false`; не object → `false`; масив → `some` рекурсивно; `node.type === 'TaggedTemplateExpression'` і `node.tag?.type === 'Identifier'` і `node.tag.name === 'gql'` → `true`; інакше для кожного `key` з `Object.keys(node)`, окрім `loc` і `range`, рекурсивна перевірка; за замовчуванням → `false`.
|
|
208
|
+
6. `export function sourceFileHasGqlTaggedTemplate(content, relativePath)` — головна перевірка: `scan = contentForGqlScan(content, relativePath)`; `pathForLang = virtualPathForParse(relativePath)`; `lang = langFromPath(pathForLang)`; в `try`: `result = parseSync(pathForLang, scan, { lang, sourceType: 'module' })`, якщо `result.errors?.length` → `false`, інакше `astContainsGqlTag(result.program)`; у `catch` → `false`.
|
|
209
|
+
7. `export function isGqlScanSourceFile(relativePath)` — повертає `SOURCE_FILE_RE.test(relativePath)`.
|
|
210
|
+
8. `export function shouldSkipFileForGqlScan(relativePosix)` — обчислює `base = relativePosix.split('/').pop() || ''`; якщо `base === 'auto-imports.d.ts'` або `base === 'components.d.ts'` → `true`; якщо `relativePosix.endsWith('.d.ts')` → `true`; інакше `false`.
|
|
211
|
+
|
|
212
|
+
**Експортується** саме три функції: `sourceFileHasGqlTaggedTemplate`, `isGqlScanSourceFile`, `shouldSkipFileForGqlScan`. Default-експорту немає.
|
|
213
|
+
|
|
214
|
+
**Поведінкові інваріанти**:
|
|
215
|
+
|
|
216
|
+
- Чисті функції (без I/O і без глобальних мутацій), окрім обережного скидання `lastIndex` на `VUE_SCRIPT_BLOCK_RE` перед `exec`-циклом.
|
|
217
|
+
- Будь-яка помилка парсера (як `errors` у відповіді, так і виняток) → результат `false`.
|
|
218
|
+
- Збіг лише на `Identifier`-тег з ім'ям `gql`; `gql.x\`…\`` **не** збігається.
|
|
219
|
+
- Ключі `loc` і `range` ігноруються при обході AST, інакше — обходяться всі property values.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# fix.mjs — entry-point правила `hasura`
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Файл `npm/rules/hasura/fix.mjs` — тонкий entry-point для правила з ідентифікатором `hasura` у складі CLI-пакета `@nitra/cursor`. Виконує дві ролі одночасно:
|
|
6
|
+
|
|
7
|
+
1. **Library mode** — експортує функцію `run(ctx)`, яка викликається з оркестратора CLI (наприклад, з `runRuleCli` або з агрегаторів типу `n-fix`), що дозволяє запускати правило в межах загального прогону всіх правил без породження окремого процесу.
|
|
8
|
+
2. **Standalone mode** — якщо файл виконується безпосередньо (через `bun rules/<id>/fix.mjs` або `node rules/<id>/fix.mjs`), він виступає самостійним CLI-входом, повністю еквівалентним `npx @nitra/cursor fix hasura`: підвантажує config, застосовує whitelist, друкує summary та повертає процесний exit-code.
|
|
9
|
+
|
|
10
|
+
Уся фактична логіка правила (послідовність кроків `applies → JS-concerns → policy → mdc-refs`) винесена в спільну реалізацію `runStandardRule` із бібліотечного шару `scripts/lib/`. Файл-обгортка несе лише прив'язку правила до власної директорії (через `import.meta.dirname`) та dispatch між двома режимами запуску.
|
|
11
|
+
|
|
12
|
+
Відповідає конвенції дворольового `fix.mjs`, ухваленій для всіх «стандартних» правил у `npm/rules/*/`, тож має мінімальну поверхню коду й не містить специфіки `hasura`: тип правила, скоупи, перевірки та поліcі описуються деінде (`meta.json`, `hasura.mdc`, `js/`, `policy/`).
|
|
13
|
+
|
|
14
|
+
## Експорти / API
|
|
15
|
+
|
|
16
|
+
| Експорт | Тип | Призначення |
|
|
17
|
+
| ------- | ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
18
|
+
| `run` | `(ctx?: RuleContext) => Promise<number>` | Library-mode прогін правила в спільному CLI orchestration. Повертає `0` при відсутності порушень або `1` при наявності порушень. |
|
|
19
|
+
|
|
20
|
+
Файл не має `default`-експорту й не експортує жодних інших символів.
|
|
21
|
+
|
|
22
|
+
Поведінка при безпосередньому запуску файлу як скрипта — побічний ефект (top-level `await runRuleCli(...)` + `process.exit(...)`) і не є частиною програмного API.
|
|
23
|
+
|
|
24
|
+
## Функції
|
|
25
|
+
|
|
26
|
+
### `run(ctx)`
|
|
27
|
+
|
|
28
|
+
Сигнатура: `export function run(ctx)`.
|
|
29
|
+
|
|
30
|
+
- **Параметри:**
|
|
31
|
+
- `ctx` (`RuleContext`, опційний) — контекст прогону, що передається з оркестратора. Тип імпортовано (через JSDoc-tag `@param`) з `../../scripts/lib/run-standard-rule.mjs`. Очікувані поля контексту (за конвенцією `runStandardRule`) — спільні для всіх правил, серед них зокрема `walkCache` (кеш обходу файлової системи, щоб не повторювати walk між кількома правилами в одному прогоні). Якщо `ctx` не передано (`undefined`), `runStandardRule` сам ініціалізує мінімально необхідний контекст.
|
|
32
|
+
- **Повертає:** `Promise<number>` — exit-code правила:
|
|
33
|
+
- `0` — застосовні файли пройшли всі етапи (`applies → JS-concerns → policy → mdc-refs`) без виявлених порушень.
|
|
34
|
+
- `1` — знайдено принаймні одне порушення (або зафіксована помилка валідації, що інтерпретується як failure).
|
|
35
|
+
- **Side effects:**
|
|
36
|
+
- Делегує всю роботу `runStandardRule(import.meta.dirname, ctx)`, який, своєю чергою, читає файли правила (`meta.json`, `*.mdc`, `js/*.mjs`, `policy/*.rego`) із директорії, обходить цільові файли проєкту, друкує діагностичні повідомлення у stdout/stderr та повертає підсумкове число порушень. Сам `run` стану не модифікує.
|
|
37
|
+
- Прив'язує правило до своєї директорії саме через `import.meta.dirname`, тож при переміщенні файлу `fix.mjs` правило автоматично «переїде» разом із його артефактами (без хардкоду шляхів).
|
|
38
|
+
|
|
39
|
+
### Безіменна процедура top-level (standalone-блок)
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
if (isRunAsCli(import.meta.url)) {
|
|
43
|
+
process.exit(await runRuleCli(import.meta.dirname))
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
- **Тип:** виконується один раз під час завантаження модуля.
|
|
48
|
+
- **Параметри:** немає (CLI-аргументи зчитуються всередині `runRuleCli` через `process.argv`).
|
|
49
|
+
- **Повертає:** нічого (виконує `process.exit`).
|
|
50
|
+
- **Side effects:**
|
|
51
|
+
- `isRunAsCli(import.meta.url)` визначає, чи модуль завантажено як прямий entry-point (а не як `import`).
|
|
52
|
+
- У випадку прямого запуску викликає `runRuleCli(import.meta.dirname)`, який повертає числовий exit-code, і негайно завершує процес із цим кодом через `process.exit`.
|
|
53
|
+
- Через `process.exit` подальші асинхронні задачі (мікротаски, відкриті ресурси) можуть бути обірвані — це свідомо: standalone entry-point повинен повертати чіткий код для CI/IDE-інтеграцій.
|
|
54
|
+
- **Локальні правила лінту:**
|
|
55
|
+
- Поряд із викликом стоїть прагма `// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE`, оскільки за загальним правилом `process.exit` заборонений, але для CLI-входів дозволений.
|
|
56
|
+
|
|
57
|
+
## Залежності
|
|
58
|
+
|
|
59
|
+
### Внутрішні (відносні імпорти)
|
|
60
|
+
|
|
61
|
+
- `../../scripts/lib/run-rule-cli.mjs`
|
|
62
|
+
- `isRunAsCli(metaUrl: string): boolean` — детектор «чи запущено модуль як скрипт». Зазвичай порівнює `import.meta.url` із URL виконуваного скрипта (`process.argv[1]`), щоб розрізнити `import` та прямий запуск.
|
|
63
|
+
- `runRuleCli(ruleDir: string): Promise<number>` — повноцінний CLI-runner: парсить аргументи, читає глобальний config (whitelist, severity, опції), запускає правило з директорії `ruleDir` і друкує summary. Повертає exit-code.
|
|
64
|
+
- `../../scripts/lib/run-standard-rule.mjs`
|
|
65
|
+
- `runStandardRule(ruleDir: string, ctx?: RuleContext): Promise<number>` — стандартна послідовність кроків для «звичайного» правила: `applies → JS-concerns → policy → mdc-refs`. Це публічне ядро правил-обгорток на кшталт `fix.mjs`.
|
|
66
|
+
- `RuleContext` (тип) — імпортовано лише в JSDoc для типізації параметра `run`.
|
|
67
|
+
|
|
68
|
+
### Зовнішні
|
|
69
|
+
|
|
70
|
+
Прямих залежностей від npm-пакетів файл не має. Усі сторонні бібліотеки, які можуть знадобитися (наприклад, walker, парсер `.mdc`, OPA-обгортки тощо), використовуються транзитивно через `run-standard-rule.mjs` та `run-rule-cli.mjs`.
|
|
71
|
+
|
|
72
|
+
### Залежність від файлів-побратимів у директорії правила
|
|
73
|
+
|
|
74
|
+
Хоча `fix.mjs` сам нічого з них не читає, `runStandardRule(import.meta.dirname)` спирається на сусідні артефакти у тій же директорії правила `hasura/`:
|
|
75
|
+
|
|
76
|
+
- `meta.json` — метадані правила (id, scope, опції тощо);
|
|
77
|
+
- `hasura.mdc` — людинозрозумілий опис правила у форматі Cursor `.mdc`;
|
|
78
|
+
- `js/` — JS-перевірки правила (`check-*.mjs`, fix-helpers);
|
|
79
|
+
- `policy/` — Rego-полісі для OPA-кроку.
|
|
80
|
+
|
|
81
|
+
Тож структурно файл `fix.mjs` має сенс лише як частина повної папки правила.
|
|
82
|
+
|
|
83
|
+
## Потік виконання / Використання
|
|
84
|
+
|
|
85
|
+
### Сценарій A — Library mode (з оркестратора)
|
|
86
|
+
|
|
87
|
+
1. Оркестратор (`n-cursor` CLI, агрегатор скілів, тести) робить `import { run } from '<path>/npm/rules/hasura/fix.mjs'`.
|
|
88
|
+
2. Викликає `await run(ctx)`, передаючи контекст із попередньо побудованим `walkCache` та іншими опціями.
|
|
89
|
+
3. `run` синхронно повертає результат виклику `runStandardRule(import.meta.dirname, ctx)`.
|
|
90
|
+
4. `runStandardRule`:
|
|
91
|
+
- читає `meta.json` із директорії правила;
|
|
92
|
+
- проганяє послідовність `applies` (фільтр цільових файлів) → JS-concerns (динамічно підвантажені `js/check-*.mjs`) → policy (OPA з `policy/*.rego`) → mdc-refs (валідація `.mdc`-посилань);
|
|
93
|
+
- агрегує знахідки й повертає кількість порушень як exit-code (`0`/`1`).
|
|
94
|
+
5. Оркестратор підсумовує exit-коди всіх правил.
|
|
95
|
+
|
|
96
|
+
### Сценарій B — Standalone CLI
|
|
97
|
+
|
|
98
|
+
1. Користувач/CI виконує `bun npm/rules/hasura/fix.mjs [args...]`.
|
|
99
|
+
2. На завантаженні модуля Node/Bun обчислює `isRunAsCli(import.meta.url)` — для прямого запуску результат `true`.
|
|
100
|
+
3. Виконується `await runRuleCli(import.meta.dirname)`:
|
|
101
|
+
- парсинг CLI-аргументів (`--whitelist`, `--severity`, прапори `-q`/`-v` тощо — деталі в `run-rule-cli.mjs`);
|
|
102
|
+
- підвантаження конфіг-файлу пакета;
|
|
103
|
+
- застосування whitelist;
|
|
104
|
+
- виклик внутрішнього еквівалента `runStandardRule` для цієї ж директорії;
|
|
105
|
+
- друк summary-таблиці результатів.
|
|
106
|
+
4. `process.exit(<exit-code>)` миттєво завершує процес зі значенням, яке повернув `runRuleCli`.
|
|
107
|
+
|
|
108
|
+
### Типові випадки використання
|
|
109
|
+
|
|
110
|
+
- **CI:** `bun npm/rules/hasura/fix.mjs` як standalone-крок у пайплайні; ненульовий exit-code зриває build.
|
|
111
|
+
- **Локально:** `npx @nitra/cursor fix hasura` запускає той самий entry-point через диспетчер CLI пакета (а не напряму).
|
|
112
|
+
- **Інтеграція в IDE:** редактор може імпортувати `run` і викликати його в окремому процесі для отримання структурованого результату по правилу `hasura` (наприклад, через виклик `bun -e "import('...fix.mjs').then(m => m.run())"`).
|
|
113
|
+
- **Агрегатори скілів** (наприклад, `n-fix`): паралельно або послідовно імпортують `run` із усіх правил `npm/rules/*/fix.mjs`, передають спільний `ctx` із `walkCache`, щоб уникнути повторного обходу файлової системи.
|
|
114
|
+
|
|
115
|
+
### Технічні зауваження щодо реалізації
|
|
116
|
+
|
|
117
|
+
- `import.meta.dirname` (Node ≥ 20.11 / Bun) повертає абсолютний шлях до директорії модуля без потреби в `fileURLToPath(import.meta.url)`. Це поточний рекомендований спосіб локалізації власної директорії в ESM-модулях.
|
|
118
|
+
- Прапор `await` у `process.exit(await runRuleCli(...))` працює завдяки top-level await у ESM — додаткової `main()`-обгортки не потрібно.
|
|
119
|
+
- Свідомо обираний `process.exit` (а не «м'який» вихід через `process.exitCode = ...`) гарантує детермінований код повернення для CI/IDE навіть якщо хтось у фоновому таску щось «недописав».
|
|
120
|
+
- Файл нейтральний щодо помилок: `runStandardRule` та `runRuleCli` самі вирішують, чи виносити exception у консоль і конвертувати її в exit-code; `fix.mjs` не обгортає виклики в `try/catch`.
|