@nitra/cursor 1.8.94 → 1.8.95

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.
@@ -0,0 +1,33 @@
1
+ ---
2
+ description: GraphQL у коді (tagged template `gql`) — GraphQL Config і розширення VS Code
3
+ alwaysApply: true
4
+ version: '1.0'
5
+ ---
6
+
7
+ Якщо в **`.vue`** або в **JavaScript / TypeScript** джерелах (`.js`, `.mjs`, `.cjs`, `.ts`, `.tsx`, `.jsx` тощо) зустрічається **tagged template literal** з тегом **`gql`** (типово `gql\`query …\`` для GraphQL-запиту), у **корені репозиторію** має бути файл **`.graphqlrc.yml`** ([GraphQL Config](https://the-guild.dev/graphql/config/docs)), а в **`.vscode/extensions.json`** у масиві **`recommendations`** — запис **`graphql.vscode-graphql`**, щоб підсвітка, навігація до схеми й діагностика працювали в редакторі.
8
+
9
+ Деталі виявлення `gql` у скриптах (у т.ч. лише `<script>` у SFC) — **`npm/scripts/check-graphql.mjs`** / **`npm/scripts/utils/graphql-gql-scan.mjs`**.
10
+
11
+ ## `.graphqlrc.yml`
12
+
13
+ Підстав свої шляхи до схеми та до файлів з операціями; приклад орієнтиру:
14
+
15
+ ```yaml title=".graphqlrc.yml"
16
+ schema: schema.graphql
17
+ documents:
18
+ - '**/*.{vue,js,ts,tsx}'
19
+ ```
20
+
21
+ ## `.vscode/extensions.json`
22
+
23
+ Додай **`graphql.vscode-graphql`** до наявного списку **`recommendations`** (не замінюй інші записи):
24
+
25
+ ```json title=".vscode/extensions.json"
26
+ {
27
+ "recommendations": ["graphql.vscode-graphql"]
28
+ }
29
+ ```
30
+
31
+ ## Перевірка
32
+
33
+ `npx @nitra/cursor check graphql`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.8.94",
3
+ "version": "1.8.95",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Перевіряє правило graphql.mdc: наявність **`.graphqlrc.yml`** і рекомендації **`graphql.vscode-graphql`**, якщо у дереві є **`gql\`…\``**.
3
+ *
4
+ * Обхід репозиторію — **`walkDir`** від **`process.cwd()`** (пропуски як у інших check). Кандидати — **`.vue`** та **`.js`/`.ts`/`.jsx`/`.tsx`** тощо; ігнор **`.d.ts`**, **auto-imports.d.ts** тощо — **`shouldSkipFileForGqlScan`**.
5
+ *
6
+ * Виявлення **`gql`** — **oxc-parser** після витягування `<script>` з SFC (**`graphql-gql-scan.mjs`**). Якщо збігів немає — перевірка завершується успішно без вимог до конфігів.
7
+ */
8
+ import { existsSync } from 'node:fs'
9
+ import { readFile } from 'node:fs/promises'
10
+ import { relative } from 'node:path'
11
+
12
+ import { createCheckReporter } from './utils/check-reporter.mjs'
13
+ import {
14
+ isGqlScanSourceFile,
15
+ shouldSkipFileForGqlScan,
16
+ sourceFileHasGqlTaggedTemplate
17
+ } from './utils/graphql-gql-scan.mjs'
18
+ import { walkDir } from './utils/walkDir.mjs'
19
+
20
+ /** Очікуваний файл GraphQL Config у корені (graphql.mdc). */
21
+ export const GRAPHQL_RC_FILENAME = '.graphqlrc.yml'
22
+
23
+ /** Розширення VS Code з graphql.mdc. */
24
+ export const REQUIRED_GRAPHQL_VSCODE_EXTENSION = 'graphql.vscode-graphql'
25
+
26
+ /**
27
+ * Перевіряє graphql.mdc: умовна вимога `.graphqlrc.yml` і `graphql.vscode-graphql` за наявності `gql`…``.
28
+ * @returns {Promise<number>} 0 — OK, 1 — порушення
29
+ */
30
+ export async function check() {
31
+ const reporter = createCheckReporter()
32
+ const { pass, fail } = reporter
33
+
34
+ const root = process.cwd()
35
+ /** @type {string[]} */
36
+ const candidates = []
37
+ await walkDir(root, absPath => {
38
+ const rel = relative(root, absPath).split('\\').join('/')
39
+ if (shouldSkipFileForGqlScan(rel) || !isGqlScanSourceFile(rel)) {
40
+ return
41
+ }
42
+ candidates.push(absPath)
43
+ })
44
+
45
+ /** @type {string[]} */
46
+ const hits = []
47
+ for (const absPath of candidates) {
48
+ const rel = relative(root, absPath).split('\\').join('/')
49
+ const content = await readFile(absPath, 'utf8')
50
+ if (sourceFileHasGqlTaggedTemplate(content, rel)) {
51
+ hits.push(rel)
52
+ }
53
+ }
54
+
55
+ if (hits.length === 0) {
56
+ pass(`Немає tagged template з тегом gql у .vue / JS / TS джерелах (переглянуто ${candidates.length} файлів) — .graphqlrc.yml не вимагається`)
57
+ return reporter.getExitCode()
58
+ }
59
+
60
+ pass(`Знайдено gql\`…\` у ${hits.length} файлі(ах): ${hits.slice(0, 5).join(', ')}${hits.length > 5 ? '…' : ''}`)
61
+
62
+ if (!existsSync(GRAPHQL_RC_FILENAME)) {
63
+ fail(
64
+ `Відсутній ${GRAPHQL_RC_FILENAME} у корені — додай GraphQL Config (graphql.mdc), бо в проєкті є gql template literals`
65
+ )
66
+ } else {
67
+ pass(`${GRAPHQL_RC_FILENAME} існує`)
68
+ }
69
+
70
+ if (!existsSync('.vscode/extensions.json')) {
71
+ fail(
72
+ '.vscode/extensions.json не існує — створи файл і додай у recommendations graphql.vscode-graphql (graphql.mdc)'
73
+ )
74
+ } else {
75
+ let ext
76
+ try {
77
+ ext = JSON.parse(await readFile('.vscode/extensions.json', 'utf8'))
78
+ } catch {
79
+ fail('.vscode/extensions.json не є валідним JSON')
80
+ ext = null
81
+ }
82
+ if (ext) {
83
+ const rec = ext.recommendations
84
+ if (!Array.isArray(rec)) {
85
+ fail('.vscode/extensions.json: поле recommendations має бути масивом')
86
+ } else if (!rec.includes(REQUIRED_GRAPHQL_VSCODE_EXTENSION)) {
87
+ fail(
88
+ `.vscode/extensions.json: додай у recommendations "${REQUIRED_GRAPHQL_VSCODE_EXTENSION}" (graphql.mdc)`
89
+ )
90
+ } else {
91
+ pass(`.vscode/extensions.json: є ${REQUIRED_GRAPHQL_VSCODE_EXTENSION}`)
92
+ }
93
+ }
94
+ }
95
+
96
+ return reporter.getExitCode()
97
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Пошук tagged template **`gql\`…\``** у джерелах для правила graphql.mdc.
3
+ *
4
+ * Для **`.vue`** береться лише вміст `<script>` / `<script setup>` (спільна логіка з **vue-forbidden-imports**).
5
+ * Семантику визначає **oxc-parser** (`program`): рекурсивний обхід AST, збіг лише для **Identifier** з іменем **`gql`** як тега шаблону.
6
+ */
7
+ import { parseSync } from 'oxc-parser'
8
+
9
+ import {
10
+ contentForVueImportScan,
11
+ isVueImportScanSourceFile,
12
+ shouldSkipFileForVueImportScan
13
+ } from './vue-forbidden-imports.mjs'
14
+
15
+ /**
16
+ * Мова для Oxc за шляхом файлу (розширення).
17
+ * @param {string} filePath віртуальний або реальний шлях
18
+ * @returns {'js' | 'jsx' | 'ts' | 'tsx'}
19
+ */
20
+ function langFromPath(filePath) {
21
+ const lower = filePath.toLowerCase()
22
+ if (lower.endsWith('.tsx')) {
23
+ return 'tsx'
24
+ }
25
+ if (lower.endsWith('.ts') || lower.endsWith('.mts') || lower.endsWith('.cts')) {
26
+ return 'ts'
27
+ }
28
+ if (lower.endsWith('.jsx')) {
29
+ return 'jsx'
30
+ }
31
+ return 'js'
32
+ }
33
+
34
+ /**
35
+ * Віртуальний шлях для парсера: SFC розбираємо як TypeScript.
36
+ * @param {string} relativePath відносний шлях до файлу
37
+ * @returns {string}
38
+ */
39
+ function virtualPathForParse(relativePath) {
40
+ if (relativePath.endsWith('.vue')) {
41
+ return relativePath.replace(/\.vue$/u, '.ts')
42
+ }
43
+ return relativePath
44
+ }
45
+
46
+ /**
47
+ * Чи містить AST хоча б один `gql` tagged template.
48
+ * @param {unknown} node корінь або вузол AST
49
+ * @returns {boolean}
50
+ */
51
+ function astContainsGqlTag(node) {
52
+ if (node === null || node === undefined) {
53
+ return false
54
+ }
55
+ if (typeof node !== 'object') {
56
+ return false
57
+ }
58
+ if (Array.isArray(node)) {
59
+ return node.some(astContainsGqlTag)
60
+ }
61
+ if (node.type === 'TaggedTemplateExpression') {
62
+ const tag = node.tag
63
+ if (tag?.type === 'Identifier' && tag.name === 'gql') {
64
+ return true
65
+ }
66
+ }
67
+ for (const key of Object.keys(node)) {
68
+ if (key === 'loc' || key === 'range') {
69
+ continue
70
+ }
71
+ if (astContainsGqlTag(node[key])) {
72
+ return true
73
+ }
74
+ }
75
+ return false
76
+ }
77
+
78
+ /**
79
+ * Перевіряє один файл: є у скрипті (або у всьому не-vue) tagged template з тегом **`gql`**.
80
+ * @param {string} content сирий вміст файлу
81
+ * @param {string} relativePath відносний шлях (posix)
82
+ * @returns {boolean} true, якщо знайдено `gql`…``
83
+ */
84
+ export function sourceFileHasGqlTaggedTemplate(content, relativePath) {
85
+ const scan = contentForVueImportScan(content, relativePath)
86
+ const pathForLang = virtualPathForParse(relativePath)
87
+ const lang = langFromPath(pathForLang)
88
+ try {
89
+ const result = parseSync(pathForLang, scan, { lang, sourceType: 'module' })
90
+ if (result.errors?.length) {
91
+ return false
92
+ }
93
+ return astContainsGqlTag(result.program)
94
+ } catch {
95
+ return false
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Чи підлягає файл скануванню за розширенням (узгоджено з vue-import scan).
101
+ * @param {string} relativePath відносний шлях
102
+ * @returns {boolean}
103
+ */
104
+ export function isGqlScanSourceFile(relativePath) {
105
+ return isVueImportScanSourceFile(relativePath)
106
+ }
107
+
108
+ /**
109
+ * Чи пропустити файл (декларації, auto-imports) — ті самі критерії, що для vue-import scan.
110
+ * @param {string} relativePosix шлях з posix-слешами
111
+ * @returns {boolean}
112
+ */
113
+ export function shouldSkipFileForGqlScan(relativePosix) {
114
+ return shouldSkipFileForVueImportScan(relativePosix)
115
+ }