@nitra/cursor 1.13.44 → 1.13.45

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,13 @@
4
4
 
5
5
  Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
6
6
 
7
+ ## [1.13.45] - 2026-05-19
8
+
9
+ ### Fixed
10
+
11
+ - `inlineTemplateLinks` tests: оновлено очікувані рядки для фікстури `__fixtures__/inline-template/fix/foo/template/snippet.json` (перейшла на форматований варіант `{ "key": "val" }` ще в 1.13.38) та для інтеграційного тесту `security.mdc` (snippet `package.json` тепер multi-line після lint-проходу). Без зміни рантайм-логіки.
12
+ - `check-ga` тестова фікстура `setupCanonicalGaProject`: додано крок `Install conftest` у `.github/workflows/lint-ga.yml`, без якого `ga.lint_ga` rego-полісі забороняє workflow і `check()` повертав 1.
13
+
7
14
  ## [1.13.44] - 2026-05-18
8
15
 
9
16
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.13.44",
3
+ "version": "1.13.45",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -3,11 +3,16 @@
3
3
  * ув'язування `.avif`-двійників з посиланнями у `.vue`/`.html`.
4
4
  *
5
5
  * Дії під час `check image-avif`:
6
- * 1. `npx \@nitra/minify-image --src=. --write --avif` генерує AVIF-двійники.
7
- * 2. У кожному workspace-пакеті переписує raster-посилання у `.vue`/`.html` на `.avif`
6
+ * 1. **Pre-scan**: знайти в `.vue`/`.html` хоча б одне raster-посилання, яке потенційно
7
+ * можна переписати на AVIF-двійник (через `import x from '...png'` або
8
+ * `<img src="...png" />`). Пакети з opt-out `disable-avif: true` пропускаються.
9
+ * Якщо жодного raster-посилання не знайдено → exit 0 одразу (`npx --avif` не запускаємо,
10
+ * rewrite/cleanup-пасс теж пропускаємо — нічого не змінилось би).
11
+ * 2. `npx \@nitra/minify-image --src=. --write --avif` — генерує AVIF-двійники.
12
+ * 3. У кожному workspace-пакеті переписує raster-посилання у `.vue`/`.html` на `.avif`
8
13
  * (де AVIF-двійник реально існує на диску). Pakety з `"\@nitra/minify-image": {
9
14
  * "disable-avif": true }` у `package.json` пропускаються.
10
- * 3. Прибирає AVIF-сироти — `<name>.<ext>.avif`, на які не лишилось жодного посилання
15
+ * 4. Прибирає AVIF-сироти — `<name>.<ext>.avif`, на які не лишилось жодного посилання
11
16
  * у `.vue`/`.html` репозиторію, видаляються (умова правила: «AVIF лишається лише
12
17
  * там, де заміна вдалася»).
13
18
  *
@@ -69,7 +74,6 @@ const VUE_RASTER_STATIC_SRC_RE = /(?<![:\-_.])\bsrc\s*=\s*['"]([^'"\s]+\.(?:png|
69
74
  * є сиротами і підлягають видаленню.
70
75
  */
71
76
  const VUE_AVIF_REF_RE = /['"]([^'"\s]+\.(?:png|jpe?g|gif)\.avif)['"]/giu
72
- const RASTER_IMAGE_EXT_RE = /\.(?:png|jpe?g|gif)$/iu
73
77
 
74
78
  /**
75
79
  * Чи у `package.json` пакета вимкнено avif-перевірку Vue-імпортів.
@@ -279,24 +283,51 @@ async function checkVueAvifImports(ignorePaths, usedAvifAbs, stats, pass, fail)
279
283
  }
280
284
 
281
285
  /**
282
- * Чи є в репозиторії хоч один raster-файл, який мав би сенс конвертувати у AVIF.
283
- * Якщо немає `npx \@nitra/minify-image` нема що робити, тож зайвий запуск пропускаємо
284
- * (важливо у тестах: фікстурні `.png`-імпорти посилаються на неіснуючі файли, тож
285
- * minify-image все одно нічого не згенерує а зайвий npx-спавн повільний і робить шум).
286
+ * Pre-scan: чи є в `.vue`/`.html` хоча б одне raster-посилання, яке потенційно треба
287
+ * переписати на AVIF-двійник (через `import x from '...png'` або `<img src="...png" />`).
288
+ *
289
+ * Якщо false весь подальший етап `image-avif` пропускаємо: ні `npx --avif`, ні rewrite,
290
+ * ні cleanup-сиріт не дали б ніяких змін. Сенс — не запускати дорогий `npx @nitra/minify-image`
291
+ * у проєктах, де AVIF не вживається (а опційно і не плануються).
292
+ *
293
+ * Скан робиться тими самими regexp-ами, що й основний rewrite-пасс (`VUE_RASTER_IMPORT_RE`
294
+ * + `VUE_RASTER_STATIC_SRC_RE`), і ходить лише по `.vue`/`.html` у workspace-пакетах, що НЕ
295
+ * мають opt-out `"@nitra/minify-image": { "disable-avif": true }` (інакше їхні шаблони ми
296
+ * все одно не сканували б, тож вони не мають провокувати запуск AVIF-етапу).
286
297
  * @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
287
- * @returns {Promise<boolean>} `true`, якщо знайдено принаймні один `.png/.jpe?g/.gif`
298
+ * @returns {Promise<boolean>} `true`, якщо знайдено принаймні одне raster-посилання
288
299
  */
289
- async function hasAnyRasterImage(ignorePaths) {
290
- let found = false
291
- await walkDir(
292
- process.cwd(),
293
- absPath => {
294
- if (found) return
295
- if (RASTER_IMAGE_EXT_RE.test(absPath)) found = true
296
- },
297
- ignorePaths
298
- )
299
- return found
300
+ async function hasAnyVueRasterReference(ignorePaths) {
301
+ const roots = await getMonorepoPackageRootDirs()
302
+ const absRootsByRel = new Map(roots.map(r => [r, join(process.cwd(), r)]))
303
+ for (const root of roots) {
304
+ const pkgPath = join(root, 'package.json')
305
+ if (existsSync(pkgPath)) {
306
+ const pkg = JSON.parse(await readFile(pkgPath, 'utf8'))
307
+ if (packageHasAvifDisabled(pkg)) continue
308
+ }
309
+ const absRoot = absRootsByRel.get(root) ?? join(process.cwd(), root)
310
+ const otherRootsAbs = roots.filter(r => r !== root && r !== '.').map(r => absRootsByRel.get(r) ?? '')
311
+ /** @type {string[]} */
312
+ const targetFiles = []
313
+ await walkDir(
314
+ absRoot,
315
+ absPath => {
316
+ if (!absPath.endsWith('.vue') && !absPath.endsWith('.html')) return
317
+ if (otherRootsAbs.some(other => absPath.startsWith(`${other}/`))) return
318
+ targetFiles.push(absPath)
319
+ },
320
+ ignorePaths
321
+ )
322
+ for (const absPath of targetFiles) {
323
+ const content = await readFile(absPath, 'utf8')
324
+ VUE_RASTER_IMPORT_RE.lastIndex = 0
325
+ if (VUE_RASTER_IMPORT_RE.test(content)) return true
326
+ VUE_RASTER_STATIC_SRC_RE.lastIndex = 0
327
+ if (VUE_RASTER_STATIC_SRC_RE.test(content)) return true
328
+ }
329
+ }
330
+ return false
300
331
  }
301
332
 
302
333
  /**
@@ -381,10 +412,15 @@ export async function check() {
381
412
 
382
413
  const ignorePaths = await loadCursorIgnorePaths(process.cwd())
383
414
 
384
- if (await hasAnyRasterImage(ignorePaths)) {
385
- runAvifGeneration()
415
+ if (!(await hasAnyVueRasterReference(ignorePaths))) {
416
+ pass(
417
+ 'image-avif: у .vue/.html немає raster-посилань для переписування — AVIF-генерація і cleanup пропущені'
418
+ )
419
+ return reporter.getExitCode()
386
420
  }
387
421
 
422
+ runAvifGeneration()
423
+
388
424
  /** @type {Set<string>} */
389
425
  const usedAvifAbs = new Set()
390
426
  /** @type {RewriteStats} */
@@ -1,17 +1,18 @@
1
1
  ---
2
2
  description: AVIF-двійники для raster-зображень з ув'язуванням у .vue/.html
3
- version: '1.3'
3
+ version: '1.4'
4
4
  globs: "**/*.{png,jpg,jpeg,gif,avif,vue,html}"
5
5
  alwaysApply: false
6
6
  ---
7
7
 
8
- AVIF-двійники (`<name>.<ext>.avif`) генерує **виключно** `npx @nitra/cursor check image-avif` — у `lint-image` прапорець `--avif` заборонений (це валідує правило `image-compress`). Перевірка робить три кроки в порядку:
8
+ AVIF-двійники (`<name>.<ext>.avif`) генерує **виключно** `npx @nitra/cursor check image-avif` — у `lint-image` прапорець `--avif` заборонений (це валідує правило `image-compress`). Перевірка робить чотири кроки в порядку:
9
9
 
10
- 1. Запускає `npx @nitra/minify-image --src=. --write --avif` (≥ **3.3.1**)генерує `<name>.<ext>.avif` поряд з кожним PNG/JPEG/GIF. CLI порівнює sha1 кожного raster-сорсу зі збереженим у `.n-minify-image.tsv` і перезаписує `<source>.avif` при зміні оригіналу.
11
- 2. Сканує `.vue` також `.html`) файли в кожному workspace-пакеті (root + workspaces) і автоматично переписує raster-посилання на AVIF-двійник у двох формах:
10
+ 1. **Pre-scan**: шукає у `.vue`/`.html` хоча б одне raster-посилання, яке потенційно треба переписати на AVIF-двійник (`import x from '...png'` або `<img src="...png" />`). Пакети з opt-out `disable-avif: true` пропускаються. **Якщо жодного raster-посилання не знайдено `check image-avif` завершується успіхом одразу: ні `npx --avif`, ні rewrite, ні cleanup-сиріт не виконуються** (нічого було б змінювати). Так уникаємо дорогого `npx @nitra/minify-image` у проєктах, де AVIF не вживається.
11
+ 2. Запускає `npx @nitra/minify-image --src=. --write --avif` (≥ **3.3.1**) генерує `<name>.<ext>.avif` поряд з кожним PNG/JPEG/GIF. CLI порівнює sha1 кожного raster-сорсу зі збереженим у `.n-minify-image.tsv` і перезаписує `<source>.avif` при зміні оригіналу.
12
+ 3. Сканує `.vue` (а також `.html`) файли в кожному workspace-пакеті (root + workspaces) і автоматично переписує raster-посилання на AVIF-двійник у двох формах:
12
13
  - **Імпорт-пов'язані** — `import x from '...png|jpg|jpeg|gif'` (далі `:src="x"` у шаблоні);
13
14
  - **Прямі статичні** — `<img src="...png" />` у `<template>` (Vite перетворює такий шлях на asset-імпорт на етапі збірки, тож вимога та сама).
14
- 3. Видаляє AVIF-сироти: ходить по всіх `<...>.avif` у репозиторії; якщо на двійник не лишилось жодного посилання у `.vue`/`.html` — файл видаляється. **AVIF на диску лишається лише там, де заміна реально відбулась** — тому невикористані оригінали не накопичують `.avif`-«хвости».
15
+ 4. Видаляє AVIF-сироти: ходить по всіх `<...>.avif` у репозиторії; якщо на двійник не лишилось жодного посилання у `.vue`/`.html` — файл видаляється. **AVIF на диску лишається лише там, де заміна реально відбулась** — тому невикористані оригінали не накопичують `.avif`-«хвости». **Зауваж**: cleanup виконується ЛИШЕ якщо pre-scan на кроці 1 знайшов хоча б одне raster-посилання — інакше осиротілі `.avif` залишаються недоторканими (видаляться вже наступним прогоном, коли в `.vue`/`.html` зʼявиться raster для конвертації).
15
16
 
16
17
  ```vue title="App.vue (після check image-avif)"
17
18
  <script setup>