@nitra/cursor 1.8.167 → 1.8.169
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 +15 -0
- package/bin/auto-rules.md +4 -0
- package/mdc/ga.mdc +6 -4
- package/mdc/image.mdc +42 -0
- package/mdc/vue.mdc +1 -1
- package/package.json +2 -2
- package/scripts/auto-rules.mjs +37 -0
- package/scripts/check-image.mjs +167 -0
- package/scripts/check-vue.mjs +67 -4
- package/scripts/lint-ga.mjs +95 -22
- package/scripts/sync-claude-config.mjs +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,21 @@
|
|
|
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.169] - 2026-05-03
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `image.mdc` (v1.2) / `check-image.mjs`: нове правило `image` для оптимізації зображень через [`@nitra/minify-image`](https://www.npmjs.com/package/@nitra/minify-image). Перевіряє лише локальну конфігурацію (CI-workflow не вимагається — sharp/svgo тягнуть бінарні залежності, цінність на ubuntu-runner-ах нижча за час прогону): скрипт `lint-image` у `package.json` з обовʼязковим викликом `npx @nitra/minify-image --src=. --write --avif` (авто-оптимізація на місці + AVIF-двійники для PNG/JPEG/GIF), `bun run lint-image` в агрегованому `lint`, заборона `@nitra/minify-image` у `dependencies`/`devDependencies` (CLI лише через `npx`, симетрично до `markdownlint-cli2` у `text.mdc`) і рядок `.minify-image-cache.tsv` у `.gitignore` (або, рідше, у `files` пакета). AVIF-двійники (`<name>.<ext>.avif`) зберігаються в git як готові артефакти для віддачі браузеру.
|
|
12
|
+
- `auto-rules.mjs` / `auto-rules.md`: введено граф залежностей між правилами (`AUTO_RULE_DEPENDENCIES`, синтаксис у `auto-rules.md` — `rule - [other]`). Правило `image` описане як `image - [vue]` — варто автододати лише разом з `vue`, без дублювання вихідної умови «`.vue`-файли». Транзитивне розгортання дозволяє ланцюги (`a → b → c`) і поважає `disable-rules` (якщо vue вимкнено — image теж не додається).
|
|
13
|
+
- `vue.mdc` (v1.4) / `check-vue.mjs`: посилено перевірку `vite.config` — окрім згадки `AutoImport` тепер вимагається, щоб у виклику `AutoImport({ imports: [...] })` був присутній рядковий елемент `'vue'`. Без цього `unplugin-auto-import` не надасть `ref` / `createApp` / тощо, і прибирати явні value-імпорти з `'vue'` стає небезпечно (зламає код). Якщо `'vue'` у `imports` відсутній — value-імпорти більше не оголошуються забороненими, а fail зʼявляється на конфізі vite. Балансована екстракція аргументів `AutoImport(...)` через `extractAutoImportCallArgs` працює для багаторядкових об'єктів.
|
|
14
|
+
|
|
15
|
+
## [1.8.168] - 2026-05-03
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- `lint-ga.mjs`: до preflight на `shellcheck` додано preflight на [`uv`](https://docs.astral.sh/uv/) (постачає `uvx` для `uvx zizmor`). Якщо `uv` відсутній у `PATH` — `n-cursor lint-ga` падає з exit 1 і підказками `brew install uv` / `curl -LsSf https://astral.sh/uv/install.sh | sh` / `pip install uv`. Обидва preflight’и повідомляються незалежно: якщо нема одночасно й `shellcheck`, і `uv`, користувач одразу бачить обидві підказки, а не лише першу.
|
|
20
|
+
- `lint-ga.mjs`: винесено внутрішній `PreflightDep` із `bin`/`winBins`/`explanation`/`install`/`successMsg` — однотипний pattern для додавання нових залежностей у preflight без копіпасти.
|
|
21
|
+
|
|
7
22
|
## [1.8.167] - 2026-05-03
|
|
8
23
|
|
|
9
24
|
### Added
|
package/bin/auto-rules.md
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
## Правила, які автоматично додається до .n-cursor.json
|
|
6
6
|
|
|
7
|
+
Синтаксис `rule - [other]` означає: правило `rule` варто автододати лише якщо всі правила у списку `[other]` вже додані до конфігу (граф залежностей між правилами; умова не дублюється).
|
|
8
|
+
|
|
7
9
|
abie - якщо в кореневому package.json в секції "repository" присутній текст "<https://github.com/abinbevefes/**/>"
|
|
8
10
|
|
|
9
11
|
bun - якщо в корені проекту є package.json
|
|
@@ -18,6 +20,8 @@ graphql - якщо хоч в одному js або vue файлі присут
|
|
|
18
20
|
|
|
19
21
|
hasura - якщо в директорії присутній config.yaml, який містить рядок `metadata_directory: metadata`
|
|
20
22
|
|
|
23
|
+
image - [vue]
|
|
24
|
+
|
|
21
25
|
js-lint - якщо присутній хоч один js файл
|
|
22
26
|
|
|
23
27
|
js-run - якщо це вкладена директорія з package.json (не в корені) та в devDependencies немає vite
|
package/mdc/ga.mdc
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Правила форматів для .github/workflows
|
|
3
3
|
alwaysApply: true
|
|
4
|
-
version: '1.
|
|
4
|
+
version: '1.6'
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
У `.github/workflows/` лише **`.yml`**. Мають бути **`clean-ga-workflows.yml`**, **`clean-merged-branch.yml`**, **`lint-ga.yml`**, **`git-ai.yml`**. Якщо є **`apply-k8s.yml`** / **`apply-nats-consumer.yml`** — paths у тригері як у фрагментах.
|
|
@@ -243,9 +243,11 @@ jobs:
|
|
|
243
243
|
|
|
244
244
|
CLI виконує:
|
|
245
245
|
|
|
246
|
-
1.
|
|
247
|
-
2. `
|
|
248
|
-
3.
|
|
246
|
+
1. Preflight на [`shellcheck`](https://www.shellcheck.net/) у `PATH` — без нього `actionlint` мовчки пропускає shell-перевірки в `run:` блоках, тож локальний прогін зеленіє, а CI на `ubuntu-latest` (де shellcheck передвстановлений) падає на тих самих workflow. Встановлення: `brew install shellcheck` (macOS), `sudo apt-get install -y shellcheck` (Debian/Ubuntu), `sudo pacman -S shellcheck` (Arch).
|
|
247
|
+
2. Preflight на [`uv`](https://docs.astral.sh/uv/) у `PATH` — постачає `uvx`, без якого не запуститься `uvx zizmor`. Встановлення: `brew install uv` (macOS), `curl -LsSf https://astral.sh/uv/install.sh | sh` (universal), `pip install uv`.
|
|
248
|
+
3. Якщо хоча б один preflight не пройшов — exit 1 (підказки для всіх відсутніх залежностей друкуються разом, а не лише для першої).
|
|
249
|
+
4. `bunx github-actionlint`.
|
|
250
|
+
5. `uvx zizmor --offline --collect=workflows .`.
|
|
249
251
|
|
|
250
252
|
**`.github/zizmor.yml`:** для [unpinned-uses](https://docs.zizmor.sh/audits/#unpinned-uses) — політика **`ref-pin`**, якщо в `uses:` семантичні теги. За потреби вимкни [template-injection](https://docs.zizmor.sh/audits/#template-injection):
|
|
251
253
|
|
package/mdc/image.mdc
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Оптимізація зображень через @nitra/minify-image у локальному lint
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
version: '1.2'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
CLI [`@nitra/minify-image`](https://www.npmjs.com/package/@nitra/minify-image) запускається через `npx` (як `markdownlint-cli2` у text.mdc) і **не** додається в `dependencies` / `devDependencies`. Канонічний `lint-image` — авто-оптимізація з прапорцями `--write --avif`: стискає raster/SVG на місці й створює AVIF-двійники (`<name>.<ext>.avif`) поряд з кожним PNG/JPEG/GIF. Кеш-файл `.minify-image-cache.tsv` робить повторні прогони дешевими.
|
|
8
|
+
|
|
9
|
+
Перевірка лише локальна — у CI `lint-image` не запускаємо (sharp/svgo тягнуть бінарні залежності, цінність на ubuntu-runner-ах нижча за час прогону). Окремий workflow `lint-image.yml` створювати не треба.
|
|
10
|
+
|
|
11
|
+
## `package.json`
|
|
12
|
+
|
|
13
|
+
```json title="package.json"
|
|
14
|
+
{
|
|
15
|
+
"scripts": {
|
|
16
|
+
"lint": "bun run lint-js && bun run lint-text && bun run lint-ga && bun run lint-image && oxfmt .",
|
|
17
|
+
"lint-image": "npx @nitra/minify-image --src=. --write --avif"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Якщо в `package.json` уже є агрегований `lint`, додай у його ланцюжок `bun run lint-image` (як `bun run lint-text`, `bun run lint-js`, `bun run lint-ga`). Так розробник, що локально гонить `bun run lint`, перед фіксацією одразу бачить, чи зросли зображення, і отримує свіжі AVIF-двійники.
|
|
23
|
+
|
|
24
|
+
## `.gitignore`
|
|
25
|
+
|
|
26
|
+
`--write` створює `.minify-image-cache.tsv` у корені сканованого каталогу — TSV з рядком на кожне зображення (`<rel-path>\t<mtime>\t<originalSize>\t<size>`). Файл **переписується** на кожному запуску, тому консервативний дефолт для бібліотек / застосунків — ігнорувати:
|
|
27
|
+
|
|
28
|
+
```text title=".gitignore"
|
|
29
|
+
.minify-image-cache.tsv
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Якщо проєкт усе ж зберігає кеш у git — це теж дозволено правилом, але тоді він має бути в `files` у `package.json` (для опублікованих npm-пакетів) або просто відсутній у `.gitignore`. Замовчування — рядок у `.gitignore`.
|
|
33
|
+
|
|
34
|
+
AVIF-двійники (`<name>.<ext>.avif`) **зберігаємо в git** — це готові артефакти для віддачі браузеру (без них ефект від `--avif` втрачається на чистому checkout-і).
|
|
35
|
+
|
|
36
|
+
## Заборонені залежності
|
|
37
|
+
|
|
38
|
+
`@nitra/minify-image` не повинен зʼявлятися ні в `dependencies`, ні в `devDependencies` кореневого `package.json` — CLI запускається лише через `npx` (так само, як `markdownlint-cli2` у text.mdc). Якщо потрібен явний пін — закладай діапазон версій npm у самому виклику (`npx @nitra/minify-image@^3 --src=. --write --avif`).
|
|
39
|
+
|
|
40
|
+
## Перевірка
|
|
41
|
+
|
|
42
|
+
`npx @nitra/cursor check image` (охоплює `lint-image` у `package.json` з обовʼязковими `--src=.`, `--write`, `--avif`, агрегований `lint` і `.minify-image-cache.tsv` у `.gitignore`).
|
package/mdc/vue.mdc
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nitra/cursor",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.169",
|
|
4
4
|
"description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -49,6 +49,6 @@
|
|
|
49
49
|
"node": ">=25"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@nitra/cursor": "^1.8.
|
|
52
|
+
"@nitra/cursor": "^1.8.169"
|
|
53
53
|
}
|
|
54
54
|
}
|
package/scripts/auto-rules.mjs
CHANGED
|
@@ -30,6 +30,7 @@ export const AUTO_RULE_ORDER = Object.freeze([
|
|
|
30
30
|
'ga',
|
|
31
31
|
'graphql',
|
|
32
32
|
'hasura',
|
|
33
|
+
'image',
|
|
33
34
|
'js-lint',
|
|
34
35
|
'js-mssql',
|
|
35
36
|
'js-bun-db',
|
|
@@ -46,6 +47,17 @@ export const AUTO_RULE_ORDER = Object.freeze([
|
|
|
46
47
|
/** Порядок автододавання skills відповідно до `auto-rules.md`. */
|
|
47
48
|
export const AUTO_SKILL_ORDER = Object.freeze(['abie-kustomize', 'fix', 'lint'])
|
|
48
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Граф залежностей між правилами (`auto-rules.md` синтаксис `rule - [other]`).
|
|
52
|
+
* Ключ варто автододати, коли всі правила-залежності вже додані до конфігу — щоб
|
|
53
|
+
* не дублювати вихідну умову, достатньо описати її у залежності.
|
|
54
|
+
*/
|
|
55
|
+
export const AUTO_RULE_DEPENDENCIES = Object.freeze(
|
|
56
|
+
/** @type {Record<string, readonly string[]>} */ ({
|
|
57
|
+
image: Object.freeze(['vue'])
|
|
58
|
+
})
|
|
59
|
+
)
|
|
60
|
+
|
|
49
61
|
const ABIE_REPOSITORY_URL_MARKER = 'https://github.com/abinbevefes/'
|
|
50
62
|
const HASURA_CONFIG_MARKER = 'metadata_directory: metadata'
|
|
51
63
|
const JS_LIKE_RE = /\.(?:mjs|cjs|js|jsx|ts|tsx)$/iu
|
|
@@ -493,6 +505,30 @@ export async function collectAutoRuleFacts(root) {
|
|
|
493
505
|
return facts
|
|
494
506
|
}
|
|
495
507
|
|
|
508
|
+
/**
|
|
509
|
+
* Транзитивно розгортає правила за `AUTO_RULE_DEPENDENCIES`: повторно проходить
|
|
510
|
+
* усіма парами «правило → залежності» доки на одному з проходів не зʼявляється
|
|
511
|
+
* нове додавання. Це дозволяє ланцюги (`a → b → c`) і не вимагає від автора правил
|
|
512
|
+
* стежити за порядком викликів `addRule`.
|
|
513
|
+
* @param {string[]} detectedRules уже зібрані id правил (мутується через addRule)
|
|
514
|
+
* @param {(ruleId: string) => void} addRule callback із спільної фабрики (поважає `disable-rules` і дублі)
|
|
515
|
+
* @returns {void}
|
|
516
|
+
*/
|
|
517
|
+
function resolveRuleDependencies(detectedRules, addRule) {
|
|
518
|
+
let changed = true
|
|
519
|
+
while (changed) {
|
|
520
|
+
changed = false
|
|
521
|
+
for (const [ruleId, deps] of Object.entries(AUTO_RULE_DEPENDENCIES)) {
|
|
522
|
+
if (detectedRules.includes(ruleId)) continue
|
|
523
|
+
if (deps.every(d => detectedRules.includes(d))) {
|
|
524
|
+
const before = detectedRules.length
|
|
525
|
+
addRule(ruleId)
|
|
526
|
+
if (detectedRules.length > before) changed = true
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
496
532
|
/**
|
|
497
533
|
* Визначає авто-правила та skills згідно з `auto-rules.md`.
|
|
498
534
|
* @param {object} params параметри аналізу
|
|
@@ -588,6 +624,7 @@ export async function detectAutoRulesAndSkills({
|
|
|
588
624
|
if (facts.hasVueSource) {
|
|
589
625
|
addRule('vue')
|
|
590
626
|
}
|
|
627
|
+
resolveRuleDependencies(detectedRules, addRule)
|
|
591
628
|
|
|
592
629
|
const autoSkillChecks = [
|
|
593
630
|
{ enabled: isAbie, id: 'abie-kustomize' },
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Перевіряє відповідність репозиторію правилу image.mdc для оптимізації зображень
|
|
3
|
+
* через `@nitra/minify-image` (локально — у CI лінт зображень не запускається).
|
|
4
|
+
*
|
|
5
|
+
* Очікування:
|
|
6
|
+
* - у кореневому `package.json` є скрипт `lint-image`, який викликає `npx @nitra/minify-image`
|
|
7
|
+
* з обовʼязковими `--src=.`, `--write` і `--avif` (авто-оптимізація з AVIF-двійниками);
|
|
8
|
+
* - якщо в `package.json` є агрегований скрипт `lint`, він викликає `bun run lint-image`
|
|
9
|
+
* (симетрично до `lint-text`, `lint-js`, `lint-ga`);
|
|
10
|
+
* - `@nitra/minify-image` не оголошений у `dependencies`/`devDependencies` —
|
|
11
|
+
* CLI запускається лише через `npx` (як `markdownlint-cli2` у `text.mdc`);
|
|
12
|
+
* - `.minify-image-cache.tsv` ігнорується через `.gitignore`,
|
|
13
|
+
* або (рідше) явно перерахований у `files` пакета npm.
|
|
14
|
+
*/
|
|
15
|
+
import { existsSync } from 'node:fs'
|
|
16
|
+
import { readFile } from 'node:fs/promises'
|
|
17
|
+
|
|
18
|
+
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
19
|
+
|
|
20
|
+
/** Імʼя CLI-пакета: рядок у `lint-image` і заборонений у залежностях. */
|
|
21
|
+
const MINIFY_PACKAGE_NAME = '@nitra/minify-image'
|
|
22
|
+
|
|
23
|
+
/** Імʼя кеш-файлу, який CLI створює у режимі `--write`. */
|
|
24
|
+
const CACHE_FILENAME = '.minify-image-cache.tsv'
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Перевіряє скрипт `lint-image` у `package.json`.
|
|
28
|
+
*
|
|
29
|
+
* Має містити виклик `npx @nitra/minify-image` з обовʼязковими прапорцями `--src=.`,
|
|
30
|
+
* `--write` (авто-оптимізація на місці) і `--avif` (AVIF-двійники для PNG/JPEG/GIF).
|
|
31
|
+
* Без `--write`/`--avif` лінт лише оцінює економію — для проєктних коммітів цього мало.
|
|
32
|
+
* @param {string|undefined} lintImage значення `scripts['lint-image']`
|
|
33
|
+
* @param {(msg: string) => void} pass callback при успішній перевірці
|
|
34
|
+
* @param {(msg: string) => void} fail callback при помилці
|
|
35
|
+
* @returns {void}
|
|
36
|
+
*/
|
|
37
|
+
function checkLintImageScript(lintImage, pass, fail) {
|
|
38
|
+
const canonical = `npx ${MINIFY_PACKAGE_NAME} --src=. --write --avif`
|
|
39
|
+
if (typeof lintImage !== 'string' || !lintImage.trim()) {
|
|
40
|
+
fail(`package.json: додай скрипт "lint-image" з \`${canonical}\` (image.mdc)`)
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
if (!lintImage.includes(`npx ${MINIFY_PACKAGE_NAME}`)) {
|
|
44
|
+
fail(`package.json: lint-image має викликати \`npx ${MINIFY_PACKAGE_NAME}\` (image.mdc)`)
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
/** @type {{ flag: string, variants: string[], hint: string }[]} */
|
|
48
|
+
const requiredFlags = [
|
|
49
|
+
{ flag: '--src=.', variants: ['--src=.', '--src .'], hint: '`--src=.`' },
|
|
50
|
+
{ flag: '--write', variants: ['--write'], hint: '`--write` (авто-оптимізація на місці)' },
|
|
51
|
+
{ flag: '--avif', variants: ['--avif'], hint: '`--avif` (AVIF-двійники для PNG/JPEG/GIF)' }
|
|
52
|
+
]
|
|
53
|
+
const missing = requiredFlags.filter(f => !f.variants.some(v => lintImage.includes(v)))
|
|
54
|
+
if (missing.length > 0) {
|
|
55
|
+
fail(
|
|
56
|
+
`package.json: lint-image має містити ${missing.map(f => f.hint).join(', ')} — канонічний виклик: \`${canonical}\` (image.mdc)`
|
|
57
|
+
)
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
pass(`package.json: lint-image викликає \`${canonical}\``)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Перевіряє, що агрегований `lint` (якщо є) кличе `bun run lint-image` —
|
|
65
|
+
* симетрично до `lint-text`, `lint-js`, `lint-ga`.
|
|
66
|
+
* @param {string|undefined} lintAggregate значення `scripts.lint`
|
|
67
|
+
* @param {(msg: string) => void} pass callback при успішній перевірці
|
|
68
|
+
* @param {(msg: string) => void} fail callback при помилці
|
|
69
|
+
* @returns {void}
|
|
70
|
+
*/
|
|
71
|
+
function checkLintAggregateIncludesImage(lintAggregate, pass, fail) {
|
|
72
|
+
if (typeof lintAggregate !== 'string' || !lintAggregate.trim()) {
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
if (lintAggregate.includes('bun run lint-image')) {
|
|
76
|
+
pass('package.json: агрегований `lint` викликає `bun run lint-image`')
|
|
77
|
+
} else {
|
|
78
|
+
fail('package.json: у `lint` додай `bun run lint-image` (image.mdc, симетрично до lint-text / lint-js / lint-ga)')
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Забороняє `@nitra/minify-image` у `dependencies` чи `devDependencies` —
|
|
84
|
+
* CLI завжди запускається через `npx` (як `markdownlint-cli2` у `text.mdc`).
|
|
85
|
+
* @param {{ dependencies?: Record<string, unknown>, devDependencies?: Record<string, unknown> }} pkg розібраний package.json
|
|
86
|
+
* @param {(msg: string) => void} pass callback при успішній перевірці
|
|
87
|
+
* @param {(msg: string) => void} fail callback при помилці
|
|
88
|
+
* @returns {void}
|
|
89
|
+
*/
|
|
90
|
+
function checkMinifyImageNotInDeps(pkg, pass, fail) {
|
|
91
|
+
const inDeps = Boolean(pkg.dependencies && MINIFY_PACKAGE_NAME in pkg.dependencies)
|
|
92
|
+
const inDevDeps = Boolean(pkg.devDependencies && MINIFY_PACKAGE_NAME in pkg.devDependencies)
|
|
93
|
+
if (inDeps || inDevDeps) {
|
|
94
|
+
fail(
|
|
95
|
+
`package.json: ${MINIFY_PACKAGE_NAME} не додавай у dependencies/devDependencies — лише через \`npx\` (image.mdc)`
|
|
96
|
+
)
|
|
97
|
+
} else {
|
|
98
|
+
pass(`package.json: ${MINIFY_PACKAGE_NAME} не оголошено в dependencies/devDependencies`)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Перевіряє `.minify-image-cache.tsv`: має бути або у `.gitignore`,
|
|
104
|
+
* або явно у `files`-листі пакета (рідкісний кейс для open-source npm-пакетів).
|
|
105
|
+
* @param {{ files?: unknown }} pkg розібраний package.json (для перевірки `files`)
|
|
106
|
+
* @param {(msg: string) => void} pass callback при успішній перевірці
|
|
107
|
+
* @param {(msg: string) => void} fail callback при помилці
|
|
108
|
+
* @returns {Promise<void>}
|
|
109
|
+
*/
|
|
110
|
+
async function checkCacheIgnoredOrPublished(pkg, pass, fail) {
|
|
111
|
+
if (existsSync('.gitignore')) {
|
|
112
|
+
const raw = await readFile('.gitignore', 'utf8')
|
|
113
|
+
const lines = raw
|
|
114
|
+
.split('\n')
|
|
115
|
+
.map(l => l.trim())
|
|
116
|
+
.filter(l => l.length > 0 && !l.startsWith('#'))
|
|
117
|
+
if (lines.includes(CACHE_FILENAME)) {
|
|
118
|
+
pass(`.gitignore містить ${CACHE_FILENAME}`)
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (Array.isArray(pkg.files) && pkg.files.some(f => typeof f === 'string' && f.includes(CACHE_FILENAME))) {
|
|
123
|
+
pass(`package.json: ${CACHE_FILENAME} перерахований у \`files\` (комітований кеш)`)
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
fail(
|
|
127
|
+
`.gitignore: додай рядок \`${CACHE_FILENAME}\` (або явно перерахуй у \`files\` package.json) — image.mdc`
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Перевіряє кореневий `package.json`: скрипти, заборонені залежності, агрегований `lint`.
|
|
133
|
+
* @param {(msg: string) => void} pass callback при успішній перевірці
|
|
134
|
+
* @param {(msg: string) => void} fail callback при помилці
|
|
135
|
+
* @returns {Promise<{ pkg: Record<string, unknown> } | null>} розібраний package.json або `null` якщо немає
|
|
136
|
+
*/
|
|
137
|
+
async function checkPackageJsonImage(pass, fail) {
|
|
138
|
+
if (!existsSync('package.json')) {
|
|
139
|
+
fail('package.json не знайдено в корені — додай (image.mdc)')
|
|
140
|
+
return null
|
|
141
|
+
}
|
|
142
|
+
const pkg = JSON.parse(await readFile('package.json', 'utf8'))
|
|
143
|
+
const scripts = /** @type {Record<string, unknown>} */ (pkg.scripts || {})
|
|
144
|
+
checkLintImageScript(typeof scripts['lint-image'] === 'string' ? scripts['lint-image'] : undefined, pass, fail)
|
|
145
|
+
checkLintAggregateIncludesImage(typeof scripts.lint === 'string' ? scripts.lint : undefined, pass, fail)
|
|
146
|
+
checkMinifyImageNotInDeps(pkg, pass, fail)
|
|
147
|
+
return { pkg }
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Перевіряє відповідність проєкту правилам `image.mdc`:
|
|
152
|
+
* `lint-image` через `npx @nitra/minify-image --src=.`, агрегований `lint`,
|
|
153
|
+
* `.minify-image-cache.tsv` у `.gitignore`. CI-workflow для image не вимагається —
|
|
154
|
+
* лінт зображень виконується лише локально.
|
|
155
|
+
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
156
|
+
*/
|
|
157
|
+
export async function check() {
|
|
158
|
+
const reporter = createCheckReporter()
|
|
159
|
+
const { pass, fail } = reporter
|
|
160
|
+
|
|
161
|
+
const pkgResult = await checkPackageJsonImage(pass, fail)
|
|
162
|
+
if (pkgResult) {
|
|
163
|
+
await checkCacheIgnoredOrPublished(pkgResult.pkg, pass, fail)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return reporter.getExitCode()
|
|
167
|
+
}
|
package/scripts/check-vue.mjs
CHANGED
|
@@ -205,19 +205,57 @@ function checkViteVersion(devDeps, prefix, passFn, fail) {
|
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
/**
|
|
209
|
+
* Витягує текст аргументів першого виклику `AutoImport(` з vite.config зі збалансованими дужками.
|
|
210
|
+
* Повертає `null`, якщо виклик не знайдено або дужки не збалансовані (тоді перевірка `'vue'`
|
|
211
|
+
* у списку `imports` пропускається — інші чек-сигнали все одно спрацюють).
|
|
212
|
+
* @param {string} content повний текст vite.config
|
|
213
|
+
* @returns {string | null} текст усередині `AutoImport(...)` без зовнішніх дужок, або `null`
|
|
214
|
+
*/
|
|
215
|
+
function extractAutoImportCallArgs(content) {
|
|
216
|
+
const marker = 'AutoImport('
|
|
217
|
+
const idx = content.indexOf(marker)
|
|
218
|
+
if (idx === -1) return null
|
|
219
|
+
const start = idx + marker.length
|
|
220
|
+
let depth = 1
|
|
221
|
+
for (let i = start; i < content.length; i++) {
|
|
222
|
+
const ch = content[i]
|
|
223
|
+
if (ch === '(') depth++
|
|
224
|
+
else if (ch === ')') {
|
|
225
|
+
depth--
|
|
226
|
+
if (depth === 0) return content.slice(start, i)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return null
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Чи передано `'vue'` (або `"vue"`) як рядковий елемент у `imports` всередині виклику `AutoImport(...)`.
|
|
234
|
+
* Без auto-import-ів `vue` забороняти явні value-імпорти `from 'vue'` небезпечно — їх видалення
|
|
235
|
+
* зламає код, бо `ref` / `createApp` тощо більше нікому надати.
|
|
236
|
+
* @param {string} content повний текст vite.config
|
|
237
|
+
* @returns {boolean} true, якщо `AutoImport({ imports: [..., 'vue', ...] })` сконфігуровано
|
|
238
|
+
*/
|
|
239
|
+
function viteConfigHasVueInAutoImports(content) {
|
|
240
|
+
const args = extractAutoImportCallArgs(content)
|
|
241
|
+
if (args === null) return false
|
|
242
|
+
return args.includes("'vue'") || args.includes('"vue"')
|
|
243
|
+
}
|
|
244
|
+
|
|
208
245
|
/**
|
|
209
246
|
* Перевіряє vite.config на наявність VueMacros і AutoImport.
|
|
210
247
|
* @param {string} rootDir параметр rootDir
|
|
211
248
|
* @param {string} prefix параметр prefix
|
|
212
249
|
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
213
250
|
* @param {(msg: string) => void} fail callback при помилці
|
|
251
|
+
* @returns {Promise<{ hasVueAutoImport: boolean }>} ознака успішно сконфігурованого vue-auto-import (для checkVueImportViolations)
|
|
214
252
|
*/
|
|
215
253
|
async function checkViteConfig(rootDir, prefix, passFn, fail) {
|
|
216
254
|
const configFiles = ['vite.config.js', 'vite.config.ts', 'vite.config.mjs']
|
|
217
255
|
const viteConfig = configFiles.find(f => existsSync(join(rootDir, f)))
|
|
218
256
|
if (!viteConfig) {
|
|
219
257
|
fail(`${prefix}немає vite.config.js|ts|mjs у каталозі пакета`)
|
|
220
|
-
return
|
|
258
|
+
return { hasVueAutoImport: false }
|
|
221
259
|
}
|
|
222
260
|
const content = await readFile(join(rootDir, viteConfig), 'utf8')
|
|
223
261
|
if (ESBUILD_RE.test(content)) {
|
|
@@ -235,23 +273,48 @@ async function checkViteConfig(rootDir, prefix, passFn, fail) {
|
|
|
235
273
|
}
|
|
236
274
|
}
|
|
237
275
|
|
|
276
|
+
const hasVueAutoImport = viteConfigHasVueInAutoImports(content)
|
|
277
|
+
if (content.includes('AutoImport(')) {
|
|
278
|
+
if (hasVueAutoImport) {
|
|
279
|
+
passFn(`${prefix}${viteConfig}: AutoImport({ imports: [..., 'vue', ...] }) — value-імпорти з 'vue' покриті`)
|
|
280
|
+
} else {
|
|
281
|
+
fail(
|
|
282
|
+
`${prefix}${viteConfig}: AutoImport не містить 'vue' у imports — додай 'vue' (інакше прибирати ` +
|
|
283
|
+
`value-імпорти на кшталт \`import { ref } from 'vue'\` небезпечно: ref/createApp тощо нікому буде надати)`
|
|
284
|
+
)
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
238
288
|
if (content.includes('process.env.npm_lifecycle_event')) {
|
|
239
289
|
fail(
|
|
240
290
|
`${prefix}${viteConfig} використовує process.env.npm_lifecycle_event — у Bun це не працює. ` +
|
|
241
291
|
`Перенеси логіку на mode (defineConfig(({ mode }) => ...)) і передавай mode в helper-функції.`
|
|
242
292
|
)
|
|
243
293
|
}
|
|
294
|
+
|
|
295
|
+
return { hasVueAutoImport }
|
|
244
296
|
}
|
|
245
297
|
|
|
246
298
|
/**
|
|
247
299
|
* Сканує джерела пакета на заборонені value-імпорти з vue.
|
|
300
|
+
*
|
|
301
|
+
* Якщо `unplugin-auto-import` не сконфігурований на `'vue'` у `vite.config`, явні value-імпорти
|
|
302
|
+
* формально не заборонені — їх видалення зламає код. У цьому випадку перевірка пропускається,
|
|
303
|
+
* а fail про відсутній `'vue'` у `AutoImport.imports` уже зареєстровано в `checkViteConfig`.
|
|
248
304
|
* @param {string} rootDir параметр rootDir
|
|
249
305
|
* @param {string} absPackageRoot параметр absPackageRoot
|
|
250
306
|
* @param {string} prefix параметр prefix
|
|
307
|
+
* @param {boolean} hasVueAutoImport чи `AutoImport({ imports: [..., 'vue', ...] })` сконфігуровано
|
|
251
308
|
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
252
309
|
* @param {(msg: string) => void} fail callback при помилці
|
|
253
310
|
*/
|
|
254
|
-
async function checkVueImportViolations(rootDir, absPackageRoot, prefix, passFn, fail) {
|
|
311
|
+
async function checkVueImportViolations(rootDir, absPackageRoot, hasVueAutoImport, prefix, passFn, fail) {
|
|
312
|
+
if (!hasVueAutoImport) {
|
|
313
|
+
passFn(
|
|
314
|
+
`${prefix}value-імпорти з 'vue' не заборонені — спершу додай 'vue' до AutoImport.imports у vite.config`
|
|
315
|
+
)
|
|
316
|
+
return
|
|
317
|
+
}
|
|
255
318
|
/** @type {string[]} */
|
|
256
319
|
const sourcePaths = []
|
|
257
320
|
await walkDir(absPackageRoot, absPath => {
|
|
@@ -323,8 +386,8 @@ async function checkVuePackage(rootDir, fail, passFn) {
|
|
|
323
386
|
'vite-plugin-vue-layouts-next відсутній — bun add -d vite-plugin-vue-layouts-next'
|
|
324
387
|
)
|
|
325
388
|
|
|
326
|
-
await checkViteConfig(rootDir, prefix, passFn, fail)
|
|
327
|
-
await checkVueImportViolations(rootDir, join(process.cwd(), rootDir), prefix, passFn, fail)
|
|
389
|
+
const { hasVueAutoImport } = await checkViteConfig(rootDir, prefix, passFn, fail)
|
|
390
|
+
await checkVueImportViolations(rootDir, join(process.cwd(), rootDir), hasVueAutoImport, prefix, passFn, fail)
|
|
328
391
|
await checkEsbuildMentions(rootDir, join(process.cwd(), rootDir), prefix, passFn, fail)
|
|
329
392
|
}
|
|
330
393
|
|
package/scripts/lint-ga.mjs
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CLI-обгортка над канонічним `lint-ga` (ga.mdc):
|
|
2
|
+
* CLI-обгортка над канонічним `lint-ga` (ga.mdc): робить preflight на `shellcheck` і `uv` (для `uvx`),
|
|
3
3
|
* тоді послідовно виконує `bunx github-actionlint` і `uvx zizmor --offline --collect=workflows .`.
|
|
4
4
|
*
|
|
5
5
|
* Без preflight `actionlint` (через `bunx github-actionlint`) мовчки пропускає shell-перевірки в
|
|
6
6
|
* `run:` блоках, коли `shellcheck` відсутній у PATH; локально `bun lint-ga` лишається зеленим, а CI
|
|
7
7
|
* на ubuntu-latest (де shellcheck передвстановлений) падає. Preflight робить цю різницю явною.
|
|
8
8
|
*
|
|
9
|
+
* `uv` потрібен для `uvx zizmor`. Якщо його нема — `uvx zizmor` падає неінформативно («command not
|
|
10
|
+
* found»); підказка з командою встановлення коротша й корисніша.
|
|
11
|
+
*
|
|
9
12
|
* Експортовано окремо `runLintGaCli` — використовується з `bin/n-cursor.js` як підкоманда `lint-ga`.
|
|
10
13
|
*/
|
|
11
14
|
import { spawnSync } from 'node:child_process'
|
|
@@ -13,28 +16,94 @@ import { platform } from 'node:process'
|
|
|
13
16
|
|
|
14
17
|
import { resolveCmd } from './utils/resolve-cmd.mjs'
|
|
15
18
|
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
]
|
|
19
|
+
/**
|
|
20
|
+
* Опис залежності preflight-ом: бінарник, для чого потрібен, і команди встановлення.
|
|
21
|
+
*
|
|
22
|
+
* @typedef {object} PreflightDep
|
|
23
|
+
* @property {string} bin ім'я виконуваного файлу (на Windows додається `.exe` за потреби)
|
|
24
|
+
* @property {string[]} winBins альтернативні імена на Windows (`shellcheck.exe`); якщо нема — fallback на `bin`
|
|
25
|
+
* @property {string} explanation 1-2 рядки про наслідки відсутності
|
|
26
|
+
* @property {string[]} install список рядків з командами встановлення (друкуються як є, з відступом)
|
|
27
|
+
* @property {string} successMsg повідомлення на pass-шлях
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/** @type {PreflightDep} */
|
|
31
|
+
const SHELLCHECK_PREFLIGHT = {
|
|
32
|
+
bin: 'shellcheck',
|
|
33
|
+
winBins: ['shellcheck.exe'],
|
|
34
|
+
explanation: [
|
|
35
|
+
'Без нього `actionlint` пропускає shell-перевірки в run: блоках,',
|
|
36
|
+
'тож локальний прогін зеленіє, а CI на ubuntu-latest (де shellcheck',
|
|
37
|
+
'передвстановлений) падає на тих самих workflow.'
|
|
38
|
+
].join('\n '),
|
|
39
|
+
install: [
|
|
40
|
+
'macOS: brew install shellcheck',
|
|
41
|
+
'Debian/Ubuntu: sudo apt-get install -y shellcheck',
|
|
42
|
+
'Arch: sudo pacman -S shellcheck'
|
|
43
|
+
],
|
|
44
|
+
successMsg: '✅ shellcheck знайдено в PATH — actionlint виконуватиме SC-правила, як у CI'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** @type {PreflightDep} */
|
|
48
|
+
const UV_PREFLIGHT = {
|
|
49
|
+
bin: 'uv',
|
|
50
|
+
winBins: ['uv.exe'],
|
|
51
|
+
explanation: [
|
|
52
|
+
'Без `uv` (а отже без `uvx`) не виконається `uvx zizmor` — second-stage аудит',
|
|
53
|
+
'workflow на ризики GitHub Actions просто не запуститься.'
|
|
54
|
+
].join('\n '),
|
|
55
|
+
install: [
|
|
56
|
+
'macOS: brew install uv',
|
|
57
|
+
'Universal: curl -LsSf https://astral.sh/uv/install.sh | sh',
|
|
58
|
+
'pip: pip install uv'
|
|
59
|
+
],
|
|
60
|
+
successMsg: '✅ uv знайдено в PATH — uvx zizmor запуститься'
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Шукає бінарник у PATH з урахуванням Windows: спершу `winBins`, потім `bin`.
|
|
65
|
+
* @param {PreflightDep} dep опис залежності
|
|
66
|
+
* @returns {string | null} абсолютний шлях або null
|
|
67
|
+
*/
|
|
68
|
+
function resolvePreflightBin(dep) {
|
|
69
|
+
if (platform === 'win32') {
|
|
70
|
+
for (const name of dep.winBins) {
|
|
71
|
+
const r = resolveCmd(name)
|
|
72
|
+
if (r) return r
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return resolveCmd(dep.bin)
|
|
76
|
+
}
|
|
22
77
|
|
|
23
78
|
/**
|
|
24
|
-
* Друкує блок з причиною fail і командами
|
|
79
|
+
* Друкує блок з причиною fail і командами встановлення.
|
|
80
|
+
* @param {PreflightDep} dep опис залежності
|
|
25
81
|
* @returns {void}
|
|
26
82
|
*/
|
|
27
|
-
function
|
|
28
|
-
console.error(
|
|
29
|
-
console.error(
|
|
30
|
-
console.error('
|
|
31
|
-
|
|
32
|
-
for (const line of SHELLCHECK_INSTALL_HINTS) {
|
|
83
|
+
function printPreflightMissingMessage(dep) {
|
|
84
|
+
console.error(`❌ ${dep.bin} не знайдено в PATH.`)
|
|
85
|
+
console.error(` ${dep.explanation}`)
|
|
86
|
+
console.error(' Встанови:')
|
|
87
|
+
for (const line of dep.install) {
|
|
33
88
|
console.error(` ${line}`)
|
|
34
89
|
}
|
|
35
90
|
console.error(' Деталі: ga.mdc → секція про lint-ga.')
|
|
36
91
|
}
|
|
37
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Запускає preflight-перевірку: pass → лог success і повертає true; fail → лог hint і повертає false.
|
|
95
|
+
* @param {PreflightDep} dep опис залежності
|
|
96
|
+
* @returns {boolean} чи знайдено бінарник
|
|
97
|
+
*/
|
|
98
|
+
function preflight(dep) {
|
|
99
|
+
if (resolvePreflightBin(dep)) {
|
|
100
|
+
console.log(dep.successMsg)
|
|
101
|
+
return true
|
|
102
|
+
}
|
|
103
|
+
printPreflightMissingMessage(dep)
|
|
104
|
+
return false
|
|
105
|
+
}
|
|
106
|
+
|
|
38
107
|
/**
|
|
39
108
|
* Запускає крок lint-ga з відображенням команди користувачу. Stdout/stderr дочірнього процесу
|
|
40
109
|
* передається користувачу як є (`stdio: 'inherit'`), щоб виглядало як прямий виклик у shell.
|
|
@@ -59,23 +128,27 @@ function runStep(title, cmd, args) {
|
|
|
59
128
|
}
|
|
60
129
|
|
|
61
130
|
/**
|
|
62
|
-
* Виконує канонічний `lint-ga` з preflight
|
|
131
|
+
* Виконує канонічний `lint-ga` з preflight-перевірками й послідовним запуском actionlint + zizmor.
|
|
63
132
|
*
|
|
64
133
|
* Послідовність:
|
|
65
|
-
* 1)
|
|
134
|
+
* 1) preflight: `shellcheck` (для actionlint SC-правил) і `uv` (для `uvx zizmor`); відсутній → exit 1;
|
|
66
135
|
* 2) `bunx github-actionlint`;
|
|
67
136
|
* 3) `uvx zizmor --offline --collect=workflows .`.
|
|
68
137
|
*
|
|
69
|
-
*
|
|
138
|
+
* Якщо хоча б один preflight не пройшов — виходимо одразу з кодом 1, **до** запуску actionlint/zizmor,
|
|
139
|
+
* бо їхні власні повідомлення про відсутність залежностей менш інформативні (особливо для shellcheck —
|
|
140
|
+
* actionlint мовчки пропускає SC-правила; ця перевірка — головний сенс обгортки).
|
|
141
|
+
*
|
|
142
|
+
* Першу помилку від actionlint/zizmor повертаємо як код виходу; наступні кроки не запускаються
|
|
143
|
+
* (відповідає `&&` у package.json).
|
|
70
144
|
* @returns {number} 0 — все OK, інакше — код першого кроку, що впав
|
|
71
145
|
*/
|
|
72
146
|
export function runLintGaCli() {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return 1
|
|
147
|
+
let preflightOk = true
|
|
148
|
+
for (const dep of [SHELLCHECK_PREFLIGHT, UV_PREFLIGHT]) {
|
|
149
|
+
if (!preflight(dep)) preflightOk = false
|
|
77
150
|
}
|
|
78
|
-
|
|
151
|
+
if (!preflightOk) return 1
|
|
79
152
|
|
|
80
153
|
const actionlintCode = runStep('actionlint', 'bunx', ['github-actionlint'])
|
|
81
154
|
if (actionlintCode !== 0) return actionlintCode
|
|
@@ -91,7 +91,7 @@ export function mergeHooks(existing, fromTemplate) {
|
|
|
91
91
|
/** @type {Record<string, HookGroup[]>} */
|
|
92
92
|
const out = {}
|
|
93
93
|
for (const [event, groups] of Object.entries(existing ?? {})) {
|
|
94
|
-
out[event] = Array.isArray(groups) ? groups
|
|
94
|
+
out[event] = Array.isArray(groups) ? [...groups] : []
|
|
95
95
|
}
|
|
96
96
|
for (const [event, templateGroups] of Object.entries(fromTemplate ?? {})) {
|
|
97
97
|
const existingGroups = (out[event] ?? []).filter(g => !isManagedHookGroup(g))
|
|
@@ -111,10 +111,10 @@ export function mergeHooks(existing, fromTemplate) {
|
|
|
111
111
|
*/
|
|
112
112
|
export function mergeSettings(existing, template) {
|
|
113
113
|
/** @type {ClaudeSettings} */
|
|
114
|
-
const merged = { ...
|
|
114
|
+
const merged = { ...existing }
|
|
115
115
|
const mergedAllow = mergeAllowList(existing?.permissions?.allow, template.permissions?.allow)
|
|
116
116
|
if (mergedAllow.length > 0) {
|
|
117
|
-
merged.permissions = { ...
|
|
117
|
+
merged.permissions = { ...existing?.permissions, allow: mergedAllow }
|
|
118
118
|
}
|
|
119
119
|
const mergedHooks = mergeHooks(existing?.hooks, template.hooks)
|
|
120
120
|
if (Object.keys(mergedHooks).length > 0) {
|
|
@@ -132,12 +132,12 @@ export function mergeSettings(existing, template) {
|
|
|
132
132
|
*/
|
|
133
133
|
async function readJsonOrUndefined(path) {
|
|
134
134
|
if (!existsSync(path)) {
|
|
135
|
-
return
|
|
135
|
+
return
|
|
136
136
|
}
|
|
137
137
|
try {
|
|
138
138
|
return JSON.parse(await readFile(path, 'utf8'))
|
|
139
139
|
} catch {
|
|
140
|
-
return
|
|
140
|
+
return
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
|