@nitra/cursor 1.8.177 → 1.8.179

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,20 @@
4
4
 
5
5
  Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
6
6
 
7
+ ## [1.8.179] - 2026-05-05
8
+
9
+ ### Changed
10
+
11
+ - `abie` (`check-abie.mjs` / `mdc/abie.mdc`): `httpHealthCheck.requestPath` у `HealthCheckPolicy` (`hc.yaml`) тепер допускає будь-який непорожній шлях від кореня — рядок, що починається з `/` (`/healthz`, `/IsAlive`, `/api/live` тощо), замість жорсткої вимоги `/healthz`. Решта вимог незмінна: `type: HTTP`, `port: 8080`, `targetRef` на headless Service з суфіксом `-hl`. Канонічно рекомендується `/healthz`, але правило не блокує сервіси з власним liveness endpoint. JSDoc у `check-abie.mjs` і опис у `mdc/abie.mdc` приведено у відповідність до коду. Тести: 3 нові кейси у `check-abie.test.mjs` (нестандартний `/IsAlive`, відсутній лідируючий `/`, порожній рядок).
12
+
13
+ ## [1.8.178] - 2026-05-05
14
+
15
+ ### Added
16
+
17
+ - `vue` (mdc v1.4 → v1.5): у `.vue` SFC заборонено імпортувати Node-нативні модулі — як з префіксом `node:` (`node:timers/promises`, `node:fs` тощо), так і bare-ім’я вбудованого модуля Node (`fs`, `path`, `crypto`, `fs/promises` …). Vue SFC виконується у браузері, де Node API недоступне; такі імпорти ламають білд / рантайм. Логіку з Node API треба виносити у server-side утіліту (backend-пакет монорепо), а у компонентах використовувати браузерні замінники (`window.crypto`, `URL`, глобальний `setTimeout`, `AbortController` тощо). Правило торкається лише `.vue` файлів — `.ts`/`.js`-утіліти, що споживаються server-side, можуть імпортувати Node-built-ins без обмежень.
18
+ - `check-vue.mjs`: нова гілка `checkVueNodeImportViolations` обходить `.vue` файли пакета (виключаючи `node_modules`/`dist`/…) і парсить `<script>` блоки тим самим **oxc-parser**’ом — для кожного `staticImport` перевіряє специфікатор через `isNodeBuiltinSpecifier(spec)` (префікс `node:` або bare-ім’я з `module.builtinModules`, з підтримкою підшляхів типу `fs/promises`). У повідомленні про fail виводиться `rel:line` і фрагмент import.
19
+ - `vue-forbidden-imports.mjs`: експортовано `isNodeBuiltinSpecifier`, `findForbiddenNodeImportsInText`, `findForbiddenNodeImportsInVueFile` (для не-`.vue` повертає `[]`). Тести: 5 нових кейсів у `vue-forbidden-imports.test.mjs` (built-in detection / `node:` префікс / bare-built-in / лише script-блоки SFC / non-`.vue` skip) та 2 нові integration-кейси у `check-rule-fixtures.test.mjs` (fail при `node:timers/promises` і при bare `fs` у SFC).
20
+
7
21
  ## [1.8.177] - 2026-05-05
8
22
 
9
23
  ### Changed
package/mdc/abie.mdc CHANGED
@@ -12,7 +12,7 @@ version: '1.17'
12
12
 
13
13
  ## k8s: `hc.yaml` поруч із Deployment
14
14
 
15
- Якщо під **`k8s`** є **Deployment**, у **тій самій директорії** має бути **`hc.yaml`** з **HealthCheckPolicy** (**`networking.gke.io/v1`**): коректний modeline **`$schema`**, **`/healthz`**, порт **8080**, **`targetRef.name`** — **headless** **Service** з суфіксом **`-hl`** (узгоджено з парою **`svc.yaml`** / **`svc-hl.yaml`** у **k8s.mdc**): або **`${metadata.name}-hl`**, або те саме ім’я, якщо **`metadata.name`** уже з **`-hl`**.
15
+ Якщо під **`k8s`** є **Deployment**, у **тій самій директорії** має бути **`hc.yaml`** з **HealthCheckPolicy** (**`networking.gke.io/v1`**): коректний modeline **`$schema`**, **`httpHealthCheck.requestPath`** — непорожній шлях від кореня (рядок, що починається з **`/`**: канонічно **`/healthz`**, але також допустимі **`/IsAlive`**, **`/api/live`** тощо — узгоджується з реальним endpoint сервісу), порт **8080**, **`targetRef.name`** — **headless** **Service** з суфіксом **`-hl`** (узгоджено з парою **`svc.yaml`** / **`svc-hl.yaml`** у **k8s.mdc**): або **`${metadata.name}-hl`**, або те саме ім’я, якщо **`metadata.name`** уже з **`-hl`**.
16
16
 
17
17
  ```yaml title="hc.yaml"
18
18
  # yaml-language-server: $schema=https://datreeio.github.io/CRDs-catalog/networking.gke.io/healthcheckpolicy_v1.json
package/mdc/vue.mdc CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: Vue
3
3
  alwaysApply: true
4
- version: '1.4'
4
+ version: '1.5'
5
5
  ---
6
6
 
7
7
  # Vue 3 Composition API — правила для .cursorrules
@@ -263,6 +263,28 @@ export default defineConfig(({ mode, command }) => {
263
263
  }
264
264
  ```
265
265
 
266
+ ## Заборонено імпортувати Node-нативні модулі у `.vue` SFC
267
+
268
+ Vue SFC виконується у браузері, тож API Node.js там недоступне. У `<script>` (включно з `<script setup>`)
269
+ заборонено будь-які імпорти вбудованих модулів Node — як з префіксом `node:`, так і bare-ім’ям модуля
270
+ (включно з підшляхами):
271
+
272
+ ```vue title="погано — ламає білд"
273
+ <script setup lang="ts">
274
+ import { setTimeout as sleep } from 'node:timers/promises'
275
+ import fs from 'fs'
276
+ import { readFile } from 'fs/promises'
277
+ import path from 'node:path'
278
+ </script>
279
+ ```
280
+
281
+ Якщо потрібна логіка з Node API — винеси її у server-side утіліту (наприклад, у backend-пакет монорепо)
282
+ та звертайся до неї через HTTP/GraphQL. Браузерні замінники (`window.crypto`, `URL`, `setTimeout` глобальний,
283
+ `AbortController` тощо) використовуй напряму, без import.
284
+
285
+ Правило стосується саме `.vue` файлів. Допоміжні `.ts`/`.js` модулі, які споживаються лише server-side
286
+ (наприклад, окремий пакет утіліт), можуть імпортувати Node-built-ins без обмежень.
287
+
266
288
  ## Перевірка
267
289
 
268
- `npx @nitra/cursor check vue` — перевіряє залежності, `vite.config`, а також обходить джерела Vue-пакета (`.vue`, `.ts`, `.js` тощо) на заборонені value-імпорти з модуля `vue`; дозволені лише type-only та side-effect `import 'vue'`. Імпорти аналізуються через **oxc-parser** (`module.staticImports`); для `.vue` вміст `<script>` витягується з SFC, далі той самий парсер (логіка в `npm/scripts/utils/vue-forbidden-imports.mjs`).
290
+ `npx @nitra/cursor check vue` — перевіряє залежності, `vite.config`, а також обходить джерела Vue-пакета (`.vue`, `.ts`, `.js` тощо) на заборонені value-імпорти з модуля `vue` (дозволені лише type-only та side-effect `import 'vue'`) і додатково сканує `.vue` SFC на імпорти Node-нативних модулів (`node:*` префікс або bare-ім’я вбудованого модуля Node — `fs`, `path`, `timers/promises` тощо). Імпорти аналізуються через **oxc-parser** (`module.staticImports`); для `.vue` вміст `<script>` витягується з SFC, далі той самий парсер (логіка в `npm/scripts/utils/vue-forbidden-imports.mjs`).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.8.177",
3
+ "version": "1.8.179",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -13,7 +13,7 @@
13
13
  *
14
14
  * **k8s:** якщо під деревом із сегментом **`k8s`** є YAML з **`kind: Deployment`**, у тій самій директорії
15
15
  * має існувати **`hc.yaml`** із **`HealthCheckPolicy`** (**`networking.gke.io/v1`**), modeline **`$schema`**
16
- * як у abie.mdc, **`/healthz`**, порт **8080**, **`targetRef`** на **headless Service** (ім'я з суфіксом **`-hl`**):
16
+ * як у abie.mdc, **`requestPath`** — непорожній шлях від кореня (рядок, що починається з **`/`**: **`/healthz`**, **`/IsAlive`**, **`/api/live`** тощо), порт **8080**, **`targetRef`** на **headless Service** (ім'я з суфіксом **`-hl`**):
17
17
  * якщо **`metadata.name`** уже закінчується на **`-hl`**, **`targetRef.name`** має збігатися з ним; інакше **`targetRef.name`** = **`${metadata.name}-hl`**.
18
18
  * Загальні вимоги до **`# yaml-language-server: $schema`** для інших YAML під **`k8s`** — у **check-k8s.mjs** / **k8s.mdc** (наприклад **HttpBackendGroup** `alb.yc.io/v1alpha1` — **без** modeline).
19
19
  * Якщо в дереві **k8s** є **HealthCheckPolicy**, перевіряється **`ru/kustomization.yaml`** з patch **`$patch: delete`**
@@ -1324,7 +1324,10 @@ function validateAbieHcPolicy(policy, relPath) {
1324
1324
  if (httpHc === null || typeof httpHc !== 'object' || Array.isArray(httpHc)) {
1325
1325
  return `${relPath}: відсутній httpHealthCheck (abie.mdc)`
1326
1326
  }
1327
- if (httpHc.requestPath !== '/healthz') return `${relPath}: httpHealthCheck.requestPath має бути /healthz (abie.mdc)`
1327
+ const requestPath = typeof httpHc.requestPath === 'string' ? httpHc.requestPath.trim() : ''
1328
+ if (requestPath === '' || !requestPath.startsWith('/')) {
1329
+ return `${relPath}: httpHealthCheck.requestPath має бути непорожнім шляхом від кореня (рядок, що починається з /) (abie.mdc)`
1330
+ }
1328
1331
  if (httpHc.port !== 8080) return `${relPath}: httpHealthCheck.port має бути 8080 (abie.mdc)`
1329
1332
  const targetRef = specRec.targetRef
1330
1333
  if (targetRef === null || typeof targetRef !== 'object' || Array.isArray(targetRef)) {
@@ -127,8 +127,8 @@ async function workspaceHasChangesAgainstBase(baseRef, ws, subWorkspaces) {
127
127
  const pathspec = pathspecForWorkspace(ws, subWorkspaces)
128
128
  try {
129
129
  await execFileAsync('git', ['diff', '--quiet', baseRef, '--', ...pathspec])
130
- } catch (err) {
131
- const code = /** @type {{ code?: number }} */ (err).code
130
+ } catch (error) {
131
+ const code = /** @type {{ code?: number }} */ (error).code
132
132
  if (code === 1) return true
133
133
  return false
134
134
  }
@@ -9,6 +9,10 @@
9
9
  *
10
10
  * Заборонені явні value-імпорти з `vue` у джерелах пакета — сканування `.vue`/`.ts`/`.js` тощо
11
11
  * через **oxc-parser** (`module.staticImports`; див. `utils/vue-forbidden-imports.mjs`); дозволені лише type-only та side-effect `import 'vue'`.
12
+ *
13
+ * Окремо в `.vue` SFC заборонено імпорти Node-нативних модулів — `node:*` префікс або bare-ім’я
14
+ * вбудованого модуля Node (`fs`, `path`, `timers/promises` тощо). Vue SFC виконується у браузері,
15
+ * де Node API недоступне; такий код треба тримати у server-side утілітах.
12
16
  */
13
17
  import { existsSync } from 'node:fs'
14
18
  import { readFile } from 'node:fs/promises'
@@ -16,6 +20,7 @@ import { join, relative } from 'node:path'
16
20
 
17
21
  import { createCheckReporter } from './utils/check-reporter.mjs'
18
22
  import {
23
+ findForbiddenNodeImportsInVueFile,
19
24
  findForbiddenVueImportsInSourceFile,
20
25
  isVueImportScanSourceFile,
21
26
  shouldSkipFileForVueImportScan
@@ -300,6 +305,49 @@ async function checkViteConfig(rootDir, prefix, passFn, fail) {
300
305
  return { hasVueAutoImport }
301
306
  }
302
307
 
308
+ /**
309
+ * Сканує `.vue` SFC пакета на заборонені імпорти Node-нативних модулів
310
+ * (`node:*` префікс або bare-ім’я вбудованого модуля Node).
311
+ * @param {string} rootDir відносний шлях до пакета
312
+ * @param {string} absPackageRoot абсолютний шлях до кореня пакета
313
+ * @param {string[]} ignorePaths шляхи, які треба пропускати при обході
314
+ * @param {string} prefix префікс повідомлень
315
+ * @param {(msg: string) => void} passFn callback при успішній перевірці
316
+ * @param {(msg: string) => void} fail callback при помилці
317
+ */
318
+ async function checkVueNodeImportViolations(rootDir, absPackageRoot, ignorePaths, prefix, passFn, fail) {
319
+ /** @type {string[]} */
320
+ const vuePaths = []
321
+ await walkDir(
322
+ absPackageRoot,
323
+ absPath => {
324
+ const rel = relative(absPackageRoot, absPath).split('\\').join('/')
325
+ if (!shouldSkipFileForVueImportScan(rel) && rel.endsWith('.vue')) {
326
+ vuePaths.push(absPath)
327
+ }
328
+ },
329
+ ignorePaths
330
+ )
331
+
332
+ let nodeImportViolations = 0
333
+ for (const absPath of vuePaths) {
334
+ const rel = relative(absPackageRoot, absPath).split('\\').join('/')
335
+ const content = await readFile(absPath, 'utf8')
336
+ for (const v of findForbiddenNodeImportsInVueFile(content, rel)) {
337
+ nodeImportViolations++
338
+ fail(
339
+ `${prefix}${rel}:${v.line} — імпорт Node-нативного модуля '${v.specifier}' у .vue заборонено ` +
340
+ `(SFC виконується в браузері, Node API недоступне). Винеси логіку у server-side утіліту. Фрагмент: ${v.snippet}`
341
+ )
342
+ }
343
+ }
344
+ if (nodeImportViolations === 0) {
345
+ passFn(
346
+ `${prefix}немає імпортів Node-нативних модулів у .vue (проскановано ${ukFilesCountPhrase(vuePaths.length)})`
347
+ )
348
+ }
349
+ }
350
+
303
351
  /**
304
352
  * Сканує джерела пакета на заборонені value-імпорти з vue.
305
353
  *
@@ -397,6 +445,7 @@ async function checkVuePackage(rootDir, ignorePaths, fail, passFn) {
397
445
 
398
446
  const { hasVueAutoImport } = await checkViteConfig(rootDir, prefix, passFn, fail)
399
447
  await checkVueImportViolations(rootDir, join(process.cwd(), rootDir), ignorePaths, hasVueAutoImport, prefix, passFn, fail)
448
+ await checkVueNodeImportViolations(rootDir, join(process.cwd(), rootDir), ignorePaths, prefix, passFn, fail)
400
449
  await checkEsbuildMentions(rootDir, join(process.cwd(), rootDir), ignorePaths, prefix, passFn, fail)
401
450
  }
402
451
 
@@ -1,5 +1,8 @@
1
1
  /**
2
- * Визначає явні імпорти з модуля `vue`, які суперечать vue.mdc (має працювати unplugin-auto-import).
2
+ * Визначає явні імпорти з модуля `vue`, які суперечать vue.mdc (має працювати unplugin-auto-import),
3
+ * а також заборонені імпорти Node-нативних модулів у `.vue` SFC (`node:*` префікс або bare ім’я
4
+ * вбудованого модуля Node — `fs`, `path`, `timers/promises` тощо). Vue SFC виконується у браузері,
5
+ * де Node API недоступне, тож такі імпорти ламають збірку/рантайм.
3
6
  *
4
7
  * Аналіз import виконується через **oxc-parser** (`parseSync`, поле `module.staticImports`) — ESTree-сумісний
5
8
  * розбір без евристик по рядках. Дозволені лише side-effect `import 'vue'`, повністю type-only імпорти
@@ -8,8 +11,12 @@
8
11
  * Для `.vue` з шаблону витягуються лише теги `<script>` / `<script setup>` (регулярний вираз); далі той самий Oxc-парсинг
9
12
  * вмісту скрипта з віртуальним ім’ям `*.ts` для режиму TypeScript.
10
13
  */
14
+ import { builtinModules } from 'node:module'
15
+
11
16
  import { parseSync } from 'oxc-parser'
12
17
 
18
+ const NODE_BUILTIN_MODULES = new Set(builtinModules)
19
+
13
20
  const VUE_EXT_RE = /\.vue$/u
14
21
  const SOURCE_FILE_RE = /\.(vue|[cm]?[jt]sx?)$/
15
22
 
@@ -179,3 +186,82 @@ export function findForbiddenVueImportsInSourceFile(content, relativePath) {
179
186
  const virtualPath = virtualPathForParse(relativePath)
180
187
  return findForbiddenVueImportsInText(scan, virtualPath)
181
188
  }
189
+
190
+ /**
191
+ * Чи є рядок-специфікатор імпортом вбудованого Node-модуля.
192
+ * Покриває обидві форми: `node:fs`, `node:timers/promises` (явний префікс) і bare-ім’я
193
+ * вбудованого модуля (`fs`, `path`, `crypto` тощо), включно з підшляхами (`fs/promises`).
194
+ * @param {string} spec значення `moduleRequest.value` (специфікатор імпорту)
195
+ * @returns {boolean} `true`, якщо це Node-нативний модуль
196
+ */
197
+ export function isNodeBuiltinSpecifier(spec) {
198
+ if (typeof spec !== 'string' || spec.length === 0) {
199
+ return false
200
+ }
201
+ if (spec.startsWith('node:')) {
202
+ return true
203
+ }
204
+ if (NODE_BUILTIN_MODULES.has(spec)) {
205
+ return true
206
+ }
207
+ const slashIdx = spec.indexOf('/')
208
+ if (slashIdx > 0) {
209
+ const head = spec.slice(0, slashIdx)
210
+ if (NODE_BUILTIN_MODULES.has(head)) {
211
+ return true
212
+ }
213
+ }
214
+ return false
215
+ }
216
+
217
+ /**
218
+ * Знаходить заборонені імпорти Node-нативних модулів у вмісті (без `<template>`).
219
+ * Vue SFC виконується у браузері, тож будь-який Node API там недоступний — навіть type-only
220
+ * імпорти збивають з пантелику (краще тримати такий код у server-side утілітах).
221
+ * @param {string} content вихідний код (для `.vue` — вже витягнуті `<script>` блоки)
222
+ * @param {string} [virtualPath] шлях для вибору `lang` (наприклад віртуальний `*.ts` після `.vue`)
223
+ * @returns {{ line: number, snippet: string, specifier: string }[]} список порушень з номером рядка
224
+ */
225
+ export function findForbiddenNodeImportsInText(content, virtualPath = 'scan.ts') {
226
+ const pathForLang = virtualPath || 'scan.ts'
227
+ const lang = langFromPath(pathForLang)
228
+ let result
229
+ try {
230
+ result = parseSync(pathForLang, content, { lang, sourceType: 'module' })
231
+ } catch {
232
+ return []
233
+ }
234
+ if (result.errors?.length) {
235
+ return []
236
+ }
237
+ /** @type {{ line: number, snippet: string, specifier: string }[]} */
238
+ const out = []
239
+ for (const imp of result.module.staticImports) {
240
+ const spec = imp.moduleRequest.value
241
+ if (isNodeBuiltinSpecifier(spec)) {
242
+ out.push({
243
+ line: offsetToLine(content, imp.start),
244
+ snippet: normalizeSnippet(content.slice(imp.start, imp.end)),
245
+ specifier: spec
246
+ })
247
+ }
248
+ }
249
+ return out
250
+ }
251
+
252
+ /**
253
+ * Знаходить заборонені імпорти Node-нативних модулів у `.vue` SFC.
254
+ * Сканує лише `<script>` блоки (template ігноруємо). Для не-`.vue` файлів повертає `[]` —
255
+ * композаблі/утіліти на Node-side можуть бути в `.ts`/`.js`, а правило стосується SFC.
256
+ * @param {string} content сирий вміст файлу
257
+ * @param {string} relativePath шлях відносно кореня пакета або репо
258
+ * @returns {{ line: number, snippet: string, specifier: string }[]} список порушень
259
+ */
260
+ export function findForbiddenNodeImportsInVueFile(content, relativePath) {
261
+ if (!relativePath.endsWith('.vue')) {
262
+ return []
263
+ }
264
+ const scan = extractVueScriptBlocks(content)
265
+ const virtualPath = virtualPathForParse(relativePath)
266
+ return findForbiddenNodeImportsInText(scan, virtualPath)
267
+ }