@nitra/cursor 12.4.0 → 12.5.0
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 +6 -0
- package/package.json +1 -1
- package/rules/bun/js/layout.mjs +1 -139
- package/rules/bun/policy/package_json/package_json.rego +1 -12
- package/rules/docker/docker.mdc +3 -7
- package/rules/docker/lint/docs/lint.md +3 -3
- package/rules/docker/meta.json +1 -1
- package/rules/docker/policy/lint_docker_yml/lint_docker_yml.rego +1 -1
- package/rules/docker/policy/lint_docker_yml/template/lint-docker.yml.snippet.yml +1 -1
- package/rules/docker/policy/package_json/package_json.rego +3 -3
- package/rules/docker/policy/package_json/template/package.json.snippet.json +1 -1
- package/rules/image-avif/image-avif.mdc +1 -1
- package/rules/image-avif/js/avif_generation.mjs +1 -1
- package/rules/image-avif/js/docs/avif_generation.md +1 -1
- package/rules/image-compress/image-compress.mdc +4 -5
- package/rules/image-compress/js/docs/index.md +1 -0
- package/rules/image-compress/js/docs/lint.md +24 -0
- package/rules/image-compress/js/docs/package_setup.md +1 -1
- package/rules/image-compress/js/lint.mjs +78 -0
- package/rules/image-compress/meta.json +1 -1
- package/rules/image-compress/policy/package_json/package_json.rego +2 -34
- package/rules/k8s/js/lint.mjs +14 -0
- package/rules/k8s/k8s.mdc +2 -12
- package/rules/k8s/lint/docs/lint.md +2 -4
- package/rules/k8s/meta.json +1 -1
- package/rules/php/js/lint.mjs +5 -3
- package/rules/php/lint/lint.mjs +3 -2
- package/rules/php/meta.json +1 -1
- package/rules/rust/js/lint.mjs +13 -1
- package/rules/rust/meta.json +1 -1
- package/rules/image-compress/policy/package_json/template/package.json.contains.json +0 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [12.5.0] - 2026-06-21
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Уніфіковано image-compress, docker і k8s lint через n-cursor lint <rule> без package.json wrappers; image-compress read-only використовує @nitra/minify-image --json.
|
|
8
|
+
|
|
3
9
|
## [12.4.0] - 2026-06-21
|
|
4
10
|
|
|
5
11
|
### Changed
|
package/package.json
CHANGED
package/rules/bun/js/layout.mjs
CHANGED
|
@@ -1,151 +1,19 @@
|
|
|
1
1
|
/** @see ./docs/layout.md */
|
|
2
2
|
import { existsSync } from 'node:fs'
|
|
3
|
-
import { readFile } from 'node:fs/promises'
|
|
4
3
|
import { join } from 'node:path'
|
|
5
4
|
|
|
6
5
|
import { createCheckReporter } from '../../../scripts/lib/check-reporter.mjs'
|
|
7
6
|
|
|
8
|
-
/** Розділювач токенів у `scripts.lint` (послідовність пробільних символів). */
|
|
9
|
-
const WHITESPACE_RE = /\s+/u
|
|
10
|
-
|
|
11
7
|
// Перевірка `devDependencies` кореневого `package.json` (дозволено лише `@nitra/*`)
|
|
12
8
|
// — у rego (`npm/policy/bun/package_json/`). JS-копії `isAllowedRootDevDependency`
|
|
13
9
|
// видалено, щоб не було двох джерел істини.
|
|
14
10
|
|
|
15
|
-
/**
|
|
16
|
-
* Зчитує `rules` та `disable-rules` з `.n-cursor.json`.
|
|
17
|
-
* @param {string} cwd корінь репозиторію
|
|
18
|
-
* @returns {Promise<{ rules: Set<string>, disabled: Set<string> }>} активні правила і явно вимкнені
|
|
19
|
-
*/
|
|
20
|
-
async function loadNCursorRules(cwd) {
|
|
21
|
-
const empty = { rules: new Set(), disabled: new Set() }
|
|
22
|
-
const cfgPath = join(cwd, '.n-cursor.json')
|
|
23
|
-
if (!existsSync(cfgPath)) return empty
|
|
24
|
-
try {
|
|
25
|
-
const raw = JSON.parse(await readFile(cfgPath, 'utf8'))
|
|
26
|
-
const list = Array.isArray(raw?.rules) ? raw.rules.map(String) : []
|
|
27
|
-
const disabled = Array.isArray(raw?.['disable-rules']) ? raw['disable-rules'].map(String) : []
|
|
28
|
-
return { rules: new Set(list), disabled: new Set(disabled) }
|
|
29
|
-
} catch {
|
|
30
|
-
return empty
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Чи містить `scripts.lint` виклик `bun run <script>` у chain'і. Шукаємо саме `bun run <script>`
|
|
36
|
-
* як окремий токен (між пробілами/`&&`), щоб уникнути false-positive на префіксах
|
|
37
|
-
* (`bun run lint-k8s-foo` не матчиться як `bun run lint-k8s`).
|
|
38
|
-
* @param {string} lintScript значення `scripts.lint` (порожній рядок — якщо нема)
|
|
39
|
-
* @param {string} target ім'я скрипта (без префіксів)
|
|
40
|
-
* @returns {boolean} true, якщо chain згадує `bun run <target>`
|
|
41
|
-
*/
|
|
42
|
-
function lintChainHasScript(lintScript, target) {
|
|
43
|
-
if (!lintScript) return false
|
|
44
|
-
const tokens = lintScript.split(WHITESPACE_RE)
|
|
45
|
-
return tokens.some((tok, i) => tok === 'bun' && tokens[i + 1] === 'run' && tokens[i + 2] === target)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Описує `lint-<id>`-обгортку та правила, що нею володіють. Один скрипт може мати кілька
|
|
50
|
-
* власників (`lint-image` — обслуговує і `image-avif`, і `image-compress`); скрипт вважається
|
|
51
|
-
* «потрібним», якщо **хоч одне** з власних правил активне у `.n-cursor.json:rules`.
|
|
52
|
-
* @typedef {object} RuleScript
|
|
53
|
-
* @property {string[]} rules id правил-власників (>=1); скрипт зобов'язаний існувати, поки активне хоч одне з них
|
|
54
|
-
* @property {string} script ім'я скрипта в `package.json:scripts`
|
|
55
|
-
* @property {string} doc `.mdc`-файл (або кома-список), на який посилається повідомлення check-у
|
|
56
|
-
*/
|
|
57
|
-
|
|
58
|
-
/** @type {RuleScript[]} */
|
|
59
|
-
const RULE_SCRIPTS = [
|
|
60
|
-
{ rules: ['docker'], script: 'lint-docker', doc: 'docker.mdc' },
|
|
61
|
-
{ rules: ['k8s'], script: 'lint-k8s', doc: 'k8s.mdc' },
|
|
62
|
-
{ rules: ['image-avif', 'image-compress'], script: 'lint-image', doc: 'image-avif.mdc / image-compress.mdc' }
|
|
63
|
-
]
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Загортає кожен ідентифікатор у backticks та зʼєднує через роздільник. Винесено
|
|
67
|
-
* окремою функцією, щоб не нестити template literals у `pass`/`fail`-повідомленнях.
|
|
68
|
-
* @param {string[]} items ідентифікатори правил
|
|
69
|
-
* @param {string} sep роздільник (наприклад `, ` або `/`)
|
|
70
|
-
* @returns {string} рядок виду "`a`, `b`"
|
|
71
|
-
*/
|
|
72
|
-
function backtickJoin(items, sep) {
|
|
73
|
-
return items.map(r => '`' + r + '`').join(sep)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Описує стан правил-власників скрипта для повідомлень про reason. Повертає або список увімкнених
|
|
78
|
-
* правил (для passing-кейсу «правило є»), або компактний опис, чому всі вимкнені (для inverse-fail).
|
|
79
|
-
* @param {string[]} owners id правил-власників (>=1)
|
|
80
|
-
* @param {{ rules: Set<string>, disabled: Set<string> }} cursorRules `rules` та `disable-rules`
|
|
81
|
-
* @returns {{ enabled: string[], reason: string }} `enabled` — список з `cursorRules.rules`; `reason` — текст для лога
|
|
82
|
-
*/
|
|
83
|
-
function ownerStatus(owners, cursorRules) {
|
|
84
|
-
const enabled = owners.filter(r => cursorRules.rules.has(r))
|
|
85
|
-
if (enabled.length > 0) {
|
|
86
|
-
return { enabled, reason: `правил${enabled.length === 1 ? 'о' : 'а'} ${backtickJoin(enabled, ', ')}` }
|
|
87
|
-
}
|
|
88
|
-
if (owners.length === 1) {
|
|
89
|
-
const [only] = owners
|
|
90
|
-
const where = cursorRules.disabled.has(only) ? 'в disable-rules' : 'відсутнє в rules'
|
|
91
|
-
return { enabled, reason: `правило \`${only}\` ${where}` }
|
|
92
|
-
}
|
|
93
|
-
const disabledCount = owners.filter(r => cursorRules.disabled.has(r)).length
|
|
94
|
-
const note = disabledCount === owners.length ? 'усі власники в disable-rules' : 'жоден власник не активний у rules'
|
|
95
|
-
return { enabled, reason: `${backtickJoin(owners, '/')} — ${note}` }
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Перевіряє двосторонній зв'язок `rules` ↔ `scripts.lint-<id>` для правил із `lint-<id>`-обгорткою
|
|
100
|
-
* (див. `RULE_SCRIPTS`). Якщо активне хоч одне правило-власник — скрипт мусить існувати; якщо
|
|
101
|
-
* жодне з власників не активне (відсутнє у `rules` або є в `disable-rules`), скрипту і згадки
|
|
102
|
-
* `bun run <script>` у `scripts.lint` бути **не може**. Інакше `bun run lint` падатиме на
|
|
103
|
-
* вимкненому правилі: `n-cursor lint-<id>` ігнорує `.n-cursor.json` і обходить дерево
|
|
104
|
-
* незалежно від конфігу (як було в cursor-репо: `disable-rules: ["k8s"]` + залишений `lint-k8s`
|
|
105
|
-
* ламав chain на template-сорцях власного правила).
|
|
106
|
-
* @param {{ pass: (msg: string) => void, fail: (msg: string) => void }} reporter callback-и `pass`/`fail` для звіту
|
|
107
|
-
* @param {Record<string, string>} scripts scripts з package.json
|
|
108
|
-
* @param {{ rules: Set<string>, disabled: Set<string> }} cursorRules `rules` та `disable-rules`
|
|
109
|
-
*/
|
|
110
|
-
function checkCursorRuleScripts(reporter, scripts, cursorRules) {
|
|
111
|
-
const { pass, fail } = reporter
|
|
112
|
-
const lintScript = typeof scripts.lint === 'string' ? scripts.lint : ''
|
|
113
|
-
for (const { rules: owners, script, doc } of RULE_SCRIPTS) {
|
|
114
|
-
const status = ownerStatus(owners, cursorRules)
|
|
115
|
-
const present = Boolean(scripts[script])
|
|
116
|
-
const inChain = lintChainHasScript(lintScript, script)
|
|
117
|
-
if (status.enabled.length > 0) {
|
|
118
|
-
if (present) {
|
|
119
|
-
pass(`package.json: є \`${script}\` (${status.reason} у .n-cursor.json)`)
|
|
120
|
-
} else {
|
|
121
|
-
fail(
|
|
122
|
-
`У .n-cursor.json увімкнено ${status.reason} — додай скрипт \`${script}\` у кореневий package.json (див. ${doc})`
|
|
123
|
-
)
|
|
124
|
-
}
|
|
125
|
-
continue
|
|
126
|
-
}
|
|
127
|
-
if (present) {
|
|
128
|
-
fail(
|
|
129
|
-
`У .n-cursor.json немає активних власників ${backtickJoin(owners, '/')} — прибери скрипт \`${script}\` з кореневого package.json (див. ${doc})`
|
|
130
|
-
)
|
|
131
|
-
}
|
|
132
|
-
if (inChain) {
|
|
133
|
-
fail(
|
|
134
|
-
`У \`scripts.lint\` є \`bun run ${script}\`, але серед \`${owners.join('/')}\` жоден не активний у .n-cursor.json — прибери з ланцюжка lint (див. ${doc})`
|
|
135
|
-
)
|
|
136
|
-
}
|
|
137
|
-
if (!present && !inChain) {
|
|
138
|
-
pass(`package.json: \`${script}\` відсутній (${status.reason})`)
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
11
|
/**
|
|
144
12
|
* Перевіряє відповідність проєкту правилам bun.mdc
|
|
145
13
|
* @param {string} [cwd] корінь репозиторію
|
|
146
14
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
147
15
|
*/
|
|
148
|
-
export
|
|
16
|
+
export function check(cwd = process.cwd()) {
|
|
149
17
|
const reporter = createCheckReporter()
|
|
150
18
|
const { pass, fail } = reporter
|
|
151
19
|
|
|
@@ -174,17 +42,11 @@ export async function check(cwd = process.cwd()) {
|
|
|
174
42
|
fail('Відсутній bunfig.toml — створи з [install] linker = "hoisted" (bun.mdc)')
|
|
175
43
|
}
|
|
176
44
|
|
|
177
|
-
const cursorRules = await loadNCursorRules(cwd)
|
|
178
|
-
|
|
179
45
|
const pkgPath = join(cwd, 'package.json')
|
|
180
46
|
if (!existsSync(pkgPath)) {
|
|
181
47
|
fail('Відсутній package.json у корені')
|
|
182
48
|
return reporter.getExitCode()
|
|
183
49
|
}
|
|
184
50
|
|
|
185
|
-
const pkg = JSON.parse(await readFile(pkgPath, 'utf8'))
|
|
186
|
-
const scripts = pkg.scripts && typeof pkg.scripts === 'object' ? pkg.scripts : {}
|
|
187
|
-
checkCursorRuleScripts(reporter, scripts, cursorRules)
|
|
188
|
-
|
|
189
51
|
return reporter.getExitCode()
|
|
190
52
|
}
|
|
@@ -9,9 +9,7 @@
|
|
|
9
9
|
# (правило `test` enabled завжди — див. `test/auto.md`; published workspace-и не мають
|
|
10
10
|
# `devDependencies` за `npm-module.mdc`)
|
|
11
11
|
#
|
|
12
|
-
# Перевірки, які
|
|
13
|
-
# - `lint-docker` / `lint-k8s` коли `.n-cursor.json:rules` містить відповідне
|
|
14
|
-
# правило (потрібен другий файл-вхід — у Rego без `--combine` не зробити).
|
|
12
|
+
# Перевірки, які потребують FS / cross-file контексту, лишаються у JS.
|
|
15
13
|
package bun.package_json
|
|
16
14
|
|
|
17
15
|
import rego.v1
|
|
@@ -47,12 +45,3 @@ allowed_root_dev_dependency(name) if {
|
|
|
47
45
|
# не мають `devDependencies` (`npm-module.mdc`).
|
|
48
46
|
name in allowed_root_test_deps
|
|
49
47
|
}
|
|
50
|
-
|
|
51
|
-
lint_prefixed_scripts := [name |
|
|
52
|
-
some name, _ in object.get(input, "scripts", {})
|
|
53
|
-
startswith(name, "lint-")
|
|
54
|
-
]
|
|
55
|
-
|
|
56
|
-
default lint_script := ""
|
|
57
|
-
|
|
58
|
-
lint_script := input.scripts.lint if is_string(input.scripts.lint)
|
package/rules/docker/docker.mdc
CHANGED
|
@@ -182,15 +182,13 @@ RUN find ./ -type f -name "*.js" -exec gzip -k {} \;
|
|
|
182
182
|
|
|
183
183
|
## lint-docker
|
|
184
184
|
|
|
185
|
-
CLI **`hadolint`** приймає лише **явні шляхи** (`[DOCKERFILE...]` у **`hadolint --help`**); обхід репозиторію робить
|
|
185
|
+
CLI **`hadolint`** приймає лише **явні шляхи** (`[DOCKERFILE...]` у **`hadolint --help`**); обхід репозиторію робить правило **`n-cursor lint docker`** (реалізація — **`npm/rules/docker/js/lint.mjs`**).
|
|
186
186
|
|
|
187
187
|
**Область lint-docker (вужча, ніж `check docker`):** лише файли з іменем **`Dockerfile`** та **`*.Dockerfile`** (суфікс **`.dockerfile`** без урахування регістру, наприклад **`api.Dockerfile`**). Файли **`Dockerfile.prod`**, **`Containerfile`** тощо **не** входять у **`lint-docker`**; їх ловить **`check docker`** (`rules/docker/fix.mjs`).
|
|
188
188
|
|
|
189
189
|
Обхід: **`walkDir`** з тими самими пропусками каталогів, що й **`rules/docker/fix.mjs`**. Виклик **`hadolint`** як **нативного бінарника** через **`ensureTool`** (PATH → кеш → авто-install brew/scoop/GitHub Release; **без** `docker run`) — спільна логіка **`npm/rules/docker/lib/docker-hadolint.mjs`**.
|
|
190
190
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
Якщо правило **`docker`** підключено в **`.n-cursor.json`** (масив **`rules`**), у **кореневому** `package.json` **обов'язково** мають бути скрипт **`lint-docker`** і виклик **`bun run lint-docker`** у агрегованому **`lint`** (див. **`bun.mdc`**). Це перевіряє **`npx @nitra/cursor fix bun`**.
|
|
191
|
+
Окремий `package.json`-скрипт `lint-docker` не потрібен і не перевіряється — єдина точка входу для правила: **`n-cursor lint docker`**.
|
|
194
192
|
|
|
195
193
|
Додай workflow **`.github/workflows/lint-docker.yml`** (гілки **`dev`** і **`main`**, лише **`.yml`**, узгоджено з **`ga.mdc`**):
|
|
196
194
|
|
|
@@ -198,11 +196,9 @@ CLI **`hadolint`** приймає лише **явні шляхи** (`[DOCKERFILE
|
|
|
198
196
|
|
|
199
197
|
Локально hadolint авто-встановлюється через **`ensureTool`** (latest, без піну версії). У CI встанови його кроком із workflow-сніпета (curl-download бінарника — без `docker run`).
|
|
200
198
|
|
|
201
|
-
Кореневий скрипт **`lint`** (див. **`n-bun.mdc`**) **обов'язково** містить **`bun run lint-docker`**, коли в проєкті підключено правило **`docker`**.
|
|
202
|
-
|
|
203
199
|
## Запуск
|
|
204
200
|
|
|
205
|
-
1. **`
|
|
201
|
+
1. **`n-cursor lint docker`** — **`js/lint.mjs`**: **`Dockerfile`** та **`*.Dockerfile`** (див. **`lint-docker`**); у CI використовуй **`n-cursor lint docker --read-only`** і встанови hadolint (приклад у workflow).
|
|
206
202
|
2. **`npx @nitra/cursor fix docker`** — **`rules/docker/fix.mjs`**, виклик hadolint як у **`docker-hadolint.mjs`** (нативний бінарник через **`ensureTool`**; **без** `docker run`).
|
|
207
203
|
3. Кореневий **`.hadolint.yaml`**: вимкнення правил, trusted registries — [документація](https://github.com/hadolint/hadolint#configure). Щоб не додавати **`# hadolint ignore=DL3007`** у кожному **`FROM`** з **`:latest`**, у корені репозиторію задати глобально:
|
|
208
204
|
|
|
@@ -148,9 +148,9 @@ if (isRunAsCli(import.meta.url)) {
|
|
|
148
148
|
|
|
149
149
|
## Потік виконання / Використання
|
|
150
150
|
|
|
151
|
-
### Послідовність дій при `n-cursor lint
|
|
151
|
+
### Послідовність дій при `n-cursor lint docker`
|
|
152
152
|
|
|
153
|
-
1. `bin/n-cursor.js` диспатчить
|
|
153
|
+
1. `bin/n-cursor.js` диспатчить правило `docker` на `npm/rules/docker/js/lint.mjs`, яке використовує `runLintDocker` із цього модуля.
|
|
154
154
|
2. `runLintDocker` → `runStandardLint(import.meta.dirname, runLintDockerSteps)`:
|
|
155
155
|
- бере серіалізаційний лок на ім’я `lint-docker`;
|
|
156
156
|
- перевіряє стан git-дерева; якщо стан збігається з попереднім успішним прогоном — крок може бути пропущено (дедуп);
|
|
@@ -167,7 +167,7 @@ if (isRunAsCli(import.meta.url)) {
|
|
|
167
167
|
|
|
168
168
|
### Як це використовується ззовні
|
|
169
169
|
|
|
170
|
-
- **CLI:** `
|
|
170
|
+
- **CLI:** `n-cursor lint docker` — основний сценарій.
|
|
171
171
|
- **Програмно з інших скриптів:**
|
|
172
172
|
|
|
173
173
|
```js
|
package/rules/docker/meta.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{ "auto": { "glob": ["**/Dockerfile", "**/Dockerfile.*"] } }
|
|
1
|
+
{ "auto": { "glob": ["**/Dockerfile", "**/Dockerfile.*"] }, "lint": "full" }
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# Канон надходить через --data: { "template": { "snippet": ... } }
|
|
4
4
|
# Структура --data сформована з template/lint-docker.yml.snippet.yml.
|
|
5
5
|
# Path-маркери, hadolint версія (substring "v2.12.0" у run), setup-bun-deps
|
|
6
|
-
# composite, `
|
|
6
|
+
# composite, `n-cursor lint docker --read-only` substring — все читається з template's snippet.
|
|
7
7
|
# Універсальні workflow-перевірки — у `ga.workflow_common`.
|
|
8
8
|
package docker.lint_docker_yml
|
|
9
9
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Перевірка `package.json` (docker.mdc).
|
|
2
2
|
#
|
|
3
3
|
# Канон надходить через --data: { "template": { "snippet": ... } }
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
4
|
+
# Backward-compatible: перевіряє ЛИШЕ зміст значення `scripts.lint-docker`, якщо ключ
|
|
5
|
+
# присутній у старому проєкті. Нові проєкти використовують `n-cursor lint docker`
|
|
6
|
+
# напряму, без package.json wrapper.
|
|
7
7
|
package docker.package_json
|
|
8
8
|
|
|
9
9
|
import rego.v1
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{ "scripts": { "lint-docker": "n-cursor lint
|
|
1
|
+
{ "scripts": { "lint-docker": "n-cursor lint docker" } }
|
|
@@ -5,7 +5,7 @@ globs: "**/*.{png,jpg,jpeg,gif,avif,vue,html}"
|
|
|
5
5
|
alwaysApply: false
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
AVIF-двійники (`<name>.<ext>.avif`) генерує **виключно** `npx @nitra/cursor fix image-avif
|
|
8
|
+
AVIF-двійники (`<name>.<ext>.avif`) генерує **виключно** `npx @nitra/cursor fix image-avif`. Правило `image-compress` відповідає лише за стиснення початкових raster/SVG-файлів через `@nitra/minify-image`, а AVIF лишається окремим правилом. Перевірка робить чотири кроки в порядку:
|
|
9
9
|
|
|
10
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
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` при зміні оригіналу.
|
|
@@ -389,7 +389,7 @@ async function cleanupOrphanAvifs(usedAvifAbs, optedOutAbs, ignorePaths, cwd) {
|
|
|
389
389
|
|
|
390
390
|
/**
|
|
391
391
|
* Виконує AVIF-етап: запуск AVIF-генерації, авто-заміна raster-посилань у `.vue`/`.html`,
|
|
392
|
-
* видалення AVIF-сиріт. Не валідує
|
|
392
|
+
* видалення AVIF-сиріт. Не валідує image-compress cache/dependency policy — це окреме правило.
|
|
393
393
|
* @param {string} [cwd] корінь репозиторію
|
|
394
394
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
395
395
|
*/
|
|
@@ -15,7 +15,7 @@ docgen:
|
|
|
15
15
|
3. **Rewrite-пасс** — для кожного workspace-пакета (без opt-out) переписує raster-посилання у `.vue`/`.html` на `<...>.avif`, якщо двійник реально існує на диску. Якщо двійника немає (наприклад, оригіналу теж нема) — фейлить конкретне посилання.
|
|
16
16
|
4. **Cleanup-пасс** — видаляє AVIF-сироти (`<...>.avif`, на які не лишилось жодного посилання у `.vue`/`.html` репозиторію), реалізуючи умову «AVIF лишається лише там, де заміна вдалася».
|
|
17
17
|
|
|
18
|
-
Модуль свідомо не дублює перевірки
|
|
18
|
+
Модуль свідомо не дублює перевірки cache/dependency policy з правила `image-compress`. Правило `image-avif` самостійне й вмикається лише там, де AVIF підтримується (адмінки), а не у публічних сайтах.
|
|
19
19
|
|
|
20
20
|
## Експорти / API
|
|
21
21
|
|
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Оптимізація raster/SVG через @nitra/minify-image у
|
|
2
|
+
description: Оптимізація raster/SVG через @nitra/minify-image у n-cursor lint
|
|
3
3
|
version: '1.2'
|
|
4
4
|
globs: "**/*.{png,jpg,jpeg,gif,svg}"
|
|
5
5
|
alwaysApply: false
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
CLI [`@nitra/minify-image`](https://www.npmjs.com/package/@nitra/minify-image) (≥ **
|
|
8
|
+
CLI [`@nitra/minify-image`](https://www.npmjs.com/package/@nitra/minify-image) (≥ **4.0.1**) запускається через `npx` і **не** додається в `dependencies` / `devDependencies`. Запуск — через **`n-cursor lint image-compress`**: локально (fix) виконує `npx @nitra/minify-image --src=. --write`, у `--read-only` виконує `npx @nitra/minify-image --src=. --json` і падає, якщо `summary.needsCompression > 0`. **AVIF-генерація (`--avif`) у `image-compress` не входить** — її виконує окреме правило `image-avif`.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Окремий workflow `lint-image.yml` створювати не треба; якщо потрібен CI-gate для зображень, використовуй `n-cursor lint image-compress --read-only`.
|
|
11
11
|
|
|
12
12
|
## `package.json`
|
|
13
13
|
|
|
14
|
-
- Канон `lint-image` (substring requirements): [package.json.contains.json](./policy/package_json/template/package.json.contains.json)
|
|
15
14
|
- Заборонені залежності `@nitra/minify-image` у deps/devDeps: [package.json.deny.json](./policy/package_json/template/package.json.deny.json)
|
|
16
15
|
|
|
17
|
-
|
|
16
|
+
Окремий `package.json`-скрипт `lint-image` не потрібен і не перевіряється — єдина точка входу `n-cursor lint image-compress`.
|
|
18
17
|
|
|
19
18
|
## Кеш
|
|
20
19
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: lint.mjs
|
|
4
|
+
resource: npm/rules/image-compress/js/lint.mjs
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Адаптер підключає `@nitra/minify-image` до єдиної точки входу `n-cursor lint image-compress`.
|
|
8
|
+
|
|
9
|
+
## Поведінка
|
|
10
|
+
|
|
11
|
+
1. У fix-режимі запускає `npx @nitra/minify-image --src=. --write`.
|
|
12
|
+
2. У read-only режимі запускає `npx @nitra/minify-image --src=. --json`.
|
|
13
|
+
3. Парсить JSON-звіт і падає, якщо `summary.needsCompression > 0`.
|
|
14
|
+
4. Повертає exit code дочірнього процесу або reporter exit code.
|
|
15
|
+
|
|
16
|
+
## Публічний API
|
|
17
|
+
|
|
18
|
+
`lint` — оркестраторний entrypoint для правила `image-compress`.
|
|
19
|
+
|
|
20
|
+
## Гарантії поведінки
|
|
21
|
+
|
|
22
|
+
- Read-only режим не стискає файли і не пише cache, покладаючись на `--json` detect-mode у `@nitra/minify-image`.
|
|
23
|
+
- Fix-режим делегує запис тільки `@nitra/minify-image --write`.
|
|
24
|
+
- За помилки запуску або невалідного JSON повертає ненульовий exit code.
|
|
@@ -18,7 +18,7 @@ docgen:
|
|
|
18
18
|
|
|
19
19
|
## Публічний API
|
|
20
20
|
|
|
21
|
-
check — Перевіряє, чи відсутній у `.gitignore` файл `.n-minify-image.tsv` та чи видалено `.minify-image-cache.tsv`.
|
|
21
|
+
check — Перевіряє, чи відсутній у `.gitignore` файл `.n-minify-image.tsv` та чи видалено `.minify-image-cache.tsv`. Package-level стискання зображень виконує `n-cursor lint image-compress`, а read-only CI-gate використовує JSON detect-mode `@nitra/minify-image --json`. (image-compress.mdc)
|
|
22
22
|
|
|
23
23
|
## Гарантії поведінки
|
|
24
24
|
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/** @see ./docs/lint.md */
|
|
2
|
+
import { spawnSync } from 'node:child_process'
|
|
3
|
+
|
|
4
|
+
import { createCheckReporter } from '../../../scripts/lib/check-reporter.mjs'
|
|
5
|
+
|
|
6
|
+
const JSON_MAX_BUFFER = 20 * 1024 * 1024
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Парсить JSON-звіт `@nitra/minify-image --json`.
|
|
10
|
+
* @param {string} stdout stdout дочірнього процесу
|
|
11
|
+
* @returns {{ summary?: { needsCompression?: unknown, total?: unknown } }} розпарсений звіт
|
|
12
|
+
*/
|
|
13
|
+
function parseMinifyJson(stdout) {
|
|
14
|
+
return JSON.parse(stdout)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Read-only detect-mode для `image-compress`: `@nitra/minify-image --json` не стискає файли і не
|
|
19
|
+
* пише cache, але повідомляє скільки зображень не відповідають `.n-minify-image.tsv`.
|
|
20
|
+
* @param {string} cwd корінь
|
|
21
|
+
* @returns {number} exit code
|
|
22
|
+
*/
|
|
23
|
+
function runJsonDetect(cwd) {
|
|
24
|
+
const reporter = createCheckReporter()
|
|
25
|
+
const { pass, fail } = reporter
|
|
26
|
+
|
|
27
|
+
const r = spawnSync('npx', ['@nitra/minify-image', '--src=.', '--json'], {
|
|
28
|
+
cwd,
|
|
29
|
+
encoding: 'utf8',
|
|
30
|
+
env: process.env,
|
|
31
|
+
maxBuffer: JSON_MAX_BUFFER
|
|
32
|
+
})
|
|
33
|
+
if (r.error) {
|
|
34
|
+
fail(`image-compress: не вдалося запустити npx @nitra/minify-image --json: ${r.error.message}`)
|
|
35
|
+
return reporter.getExitCode()
|
|
36
|
+
}
|
|
37
|
+
if (r.status !== 0) {
|
|
38
|
+
const detail = [r.stdout, r.stderr].filter(Boolean).join('\n').trim()
|
|
39
|
+
fail(`image-compress: @nitra/minify-image --json завершився з кодом ${r.status}${detail ? `:\n${detail}` : ''}`)
|
|
40
|
+
return reporter.getExitCode()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let report
|
|
44
|
+
try {
|
|
45
|
+
report = parseMinifyJson(r.stdout)
|
|
46
|
+
} catch {
|
|
47
|
+
fail('image-compress: @nitra/minify-image --json повернув невалідний JSON')
|
|
48
|
+
return reporter.getExitCode()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const needsCompression = Number(report.summary?.needsCompression ?? 0)
|
|
52
|
+
const total = Number(report.summary?.total ?? 0)
|
|
53
|
+
if (needsCompression > 0) {
|
|
54
|
+
fail(
|
|
55
|
+
`image-compress: ${needsCompression}/${total} image-файлів потребують стиснення — запусти \`n-cursor lint image-compress\` локально`
|
|
56
|
+
)
|
|
57
|
+
} else {
|
|
58
|
+
pass(`image-compress: ${total} image-файлів синхронізовані з .n-minify-image.tsv`)
|
|
59
|
+
}
|
|
60
|
+
return reporter.getExitCode()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Оркестраторний адаптер `n-cursor lint image-compress`.
|
|
65
|
+
* @param {string[] | undefined} _files ігнорується; `@nitra/minify-image` сам обходить дерево
|
|
66
|
+
* @param {string} [cwd] корінь
|
|
67
|
+
* @param {{ readOnly?: boolean }} [opts] readOnly → `--json`, fix → `--write`
|
|
68
|
+
* @returns {Promise<number>} exit code
|
|
69
|
+
*/
|
|
70
|
+
export function lint(_files, cwd = process.cwd(), opts = {}) {
|
|
71
|
+
if (opts.readOnly === true) return Promise.resolve(runJsonDetect(cwd))
|
|
72
|
+
const r = spawnSync('npx', ['@nitra/minify-image', '--src=.', '--write'], { cwd, env: process.env, stdio: 'inherit' })
|
|
73
|
+
if (r.error) {
|
|
74
|
+
console.error(`image-compress: не вдалося запустити npx @nitra/minify-image --write: ${r.error.message}`)
|
|
75
|
+
return Promise.resolve(1)
|
|
76
|
+
}
|
|
77
|
+
return Promise.resolve(typeof r.status === 'number' ? r.status : 1)
|
|
78
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{ "auto": { "glob": "**/*.{png,jpg,jpeg,gif,svg}" } }
|
|
1
|
+
{ "auto": { "glob": "**/*.{png,jpg,jpeg,gif,svg}" }, "lint": "full" }
|
|
@@ -1,25 +1,11 @@
|
|
|
1
1
|
# Перевірка `package.json` (image-compress.mdc).
|
|
2
2
|
#
|
|
3
|
-
# Канон надходить через --data: { "template": { "
|
|
4
|
-
# Структура --data сформована з template/package.json.
|
|
5
|
-
#
|
|
6
|
-
# Логіка, що ЛИШАЄТЬСЯ у rego (inverse-patterns, не виносяться у template):
|
|
7
|
-
# - `--avif` ЗАБОРОНЕНИЙ підрядок у `lint-image` (anti-contains);
|
|
8
|
-
# - агрегатор `lint` (якщо `lint-image` присутній) має містити `bun run lint-image`.
|
|
3
|
+
# Канон надходить через --data: { "template": { "deny": ... } }.
|
|
4
|
+
# Структура --data сформована з template/package.json.deny.json.
|
|
9
5
|
package image_compress.package_json
|
|
10
6
|
|
|
11
7
|
import rego.v1
|
|
12
8
|
|
|
13
|
-
# ── deny: scripts.<name> має містити кожен substring з template.contains ─
|
|
14
|
-
|
|
15
|
-
deny contains msg if {
|
|
16
|
-
some script_name, needles in data.template.contains.scripts
|
|
17
|
-
actual := object.get(object.get(input, "scripts", {}), script_name, "")
|
|
18
|
-
some needle in needles
|
|
19
|
-
not contains(actual, needle)
|
|
20
|
-
msg := sprintf("package.json: scripts.%s має містити %q (image-compress.mdc)", [script_name, needle])
|
|
21
|
-
}
|
|
22
|
-
|
|
23
9
|
# ── deny: top-level deps/devDeps з template.deny ─────────────────────────
|
|
24
10
|
|
|
25
11
|
deny contains msg if {
|
|
@@ -33,21 +19,3 @@ deny contains msg if {
|
|
|
33
19
|
pkg in object.keys(object.get(input, "devDependencies", {}))
|
|
34
20
|
msg := sprintf("package.json: devDependencies.%s — %s", [pkg, reason])
|
|
35
21
|
}
|
|
36
|
-
|
|
37
|
-
# ── deny: `--avif` заборонений у `lint-image` (anti-contains, у rego) ────
|
|
38
|
-
|
|
39
|
-
deny contains msg if {
|
|
40
|
-
lint_image := object.get(object.get(input, "scripts", {}), "lint-image", "")
|
|
41
|
-
contains(lint_image, "--avif")
|
|
42
|
-
msg := "package.json: lint-image не має містити `--avif` — AVIF-генерацію виконує check image-avif (image-compress.mdc)"
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
# ── deny: агрегатор `lint` (якщо `lint-image` є) ─────────────────────────
|
|
46
|
-
|
|
47
|
-
deny contains msg if {
|
|
48
|
-
"lint-image" in object.keys(object.get(input, "scripts", {}))
|
|
49
|
-
lint := object.get(object.get(input, "scripts", {}), "lint", "")
|
|
50
|
-
lint != ""
|
|
51
|
-
not contains(lint, "bun run lint-image")
|
|
52
|
-
msg := "package.json: агрегований `lint` має містити `bun run lint-image` (image-compress.mdc)"
|
|
53
|
-
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** @see ./docs/lint.md */
|
|
2
|
+
import { runLintK8s } from '../lint/lint.mjs'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Оркестраторний адаптер `n-cursor lint k8s` (лінтер-фаза): kubeconform + kubescape по деревах
|
|
6
|
+
* `.../k8s/*.yaml` через `runLintK8s` (read-only тули — мутацій немає, тож `opts` ігнорується).
|
|
7
|
+
* Структурні k8s.mdc-перевірки (manifest/kustomization/network_policy) — у конформність-фазі.
|
|
8
|
+
* Без `.../k8s`-маніфестів крок — no-op.
|
|
9
|
+
* @param {string[] | undefined} _files ігнорується (whole-repo обхід `.../k8s`)
|
|
10
|
+
* @returns {Promise<number>} exit code
|
|
11
|
+
*/
|
|
12
|
+
export function lint(_files) {
|
|
13
|
+
return runLintK8s()
|
|
14
|
+
}
|
package/rules/k8s/k8s.mdc
CHANGED
|
@@ -43,17 +43,7 @@ alwaysApply: false
|
|
|
43
43
|
|
|
44
44
|
Підстав свою `attributes.name` (рядок або regex), якщо ConfigMap зветься інакше; виключай контрольно, а не глобально (не додавай винятки без `attributes.name`/`labels`, бо тоді C-0012 знімається для усіх ConfigMap-ів проєкту і реальні витоки credentials теж пройдуть).
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
```json title="package.json"
|
|
49
|
-
{
|
|
50
|
-
"scripts": {
|
|
51
|
-
"lint-k8s": "n-cursor lint-k8s"
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Якщо правило **`k8s`** підключено в **`.n-cursor.json`** (масив **`rules`**), у **кореневому** `package.json` **мають** бути скрипт **`lint-k8s`** і виклик **`bun run lint-k8s`** у агрегованому **`lint`** (див. **`bun.mdc`**). Це перевіряє **`npx @nitra/cursor fix bun`**.
|
|
46
|
+
Лінт запускається через правило **`n-cursor lint k8s`** (реалізація — **`npm/rules/k8s/js/lint.mjs`**, делегує kubeconform/kubescape у **`npm/rules/k8s/lint/lint.mjs`**). Окремий `package.json`-скрипт `lint-k8s` не потрібен і не перевіряється.
|
|
57
47
|
|
|
58
48
|
Додай workflow **`.github/workflows/lint-k8s.yml`** (гілки **`dev`** і **`main`**, лише **`.yml`**, узгоджено з **`ga.mdc`**). **Не** дублюй **`setup-node`**, **`oven-sh/setup-bun`**, **`actions/cache`** і **`bun install`** у job — після **`checkout`** використовуй локальний composite **`setup-bun-deps`** (шлях **`./.github/actions/setup-bun-deps`** або **`./npm/github-actions/setup-bun-deps`**, як у репозиторії). Встановлення **kubeconform** / **kubescape** лишаються окремими кроками.
|
|
59
49
|
|
|
@@ -103,7 +93,7 @@ jobs:
|
|
|
103
93
|
# (kubectl уже доступний на github-hosted runner'ах: https://github.com/actions/runner-images).
|
|
104
94
|
|
|
105
95
|
- name: Lint K8s
|
|
106
|
-
run:
|
|
96
|
+
run: n-cursor lint k8s --read-only
|
|
107
97
|
```
|
|
108
98
|
|
|
109
99
|
## Deployment: `resources.requests` (CPU і memory)
|
|
@@ -317,12 +317,10 @@ CLI-режим: при прямому виконанні скрипта (`bun np
|
|
|
317
317
|
|
|
318
318
|
## Потік виконання / Використання
|
|
319
319
|
|
|
320
|
-
### CLI-режим
|
|
320
|
+
### CLI-режим
|
|
321
321
|
|
|
322
322
|
```
|
|
323
|
-
|
|
324
|
-
# або
|
|
325
|
-
n-cursor lint-k8s # CLI-обгортка
|
|
323
|
+
n-cursor lint k8s # rule orchestration entrypoint
|
|
326
324
|
# або (наприкінці файлу — прямий запуск)
|
|
327
325
|
bun npm/rules/k8s/lint/lint.mjs
|
|
328
326
|
```
|
package/rules/k8s/meta.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{ "auto": { "glob": "**/k8s/**" } }
|
|
1
|
+
{ "auto": { "glob": "**/k8s/**" }, "lint": "full" }
|
package/rules/php/js/lint.mjs
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
/** @see ./docs/lint.md */
|
|
2
2
|
import { run } from '../lint/lint.mjs'
|
|
3
|
+
import { runStandardLint } from '../../../scripts/lib/run-standard-lint.mjs'
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Оркестраторний адаптер `n-cursor lint php` (лінтер-фаза): composer audit + php-cs-fixer
|
|
6
7
|
* (`--dry-run`) + phpstan/psalm через `run` (read-only — мутацій немає, тож `opts` ігнорується).
|
|
7
8
|
* Структурні php.mdc-перевірки — у конформність-фазі. Без composer-інструментів крок — no-op.
|
|
8
9
|
* @param {string[] | undefined} _files ігнорується (whole-repo обхід)
|
|
9
|
-
* @
|
|
10
|
+
* @param {string} [cwd] корінь
|
|
11
|
+
* @returns {Promise<number>} exit code
|
|
10
12
|
*/
|
|
11
|
-
export function lint(_files) {
|
|
12
|
-
return run()
|
|
13
|
+
export function lint(_files, cwd = process.cwd()) {
|
|
14
|
+
return runStandardLint(import.meta.dirname, () => run(cwd))
|
|
13
15
|
}
|
package/rules/php/lint/lint.mjs
CHANGED
|
@@ -63,13 +63,14 @@ function runTool(label, abs, args, pass, fail) {
|
|
|
63
63
|
|
|
64
64
|
/**
|
|
65
65
|
* Запускає `lint-php`.
|
|
66
|
+
* @param {string} [cwd] корінь репозиторію
|
|
66
67
|
* @returns {number} 0 — OK, 1 — є помилки
|
|
67
68
|
*/
|
|
68
|
-
export function run() {
|
|
69
|
+
export function run(cwd = process.cwd()) {
|
|
69
70
|
const reporter = createCheckReporter()
|
|
70
71
|
const { pass, fail } = reporter
|
|
71
72
|
|
|
72
|
-
const root =
|
|
73
|
+
const root = cwd
|
|
73
74
|
if (!existsSync(join(root, 'composer.json'))) {
|
|
74
75
|
pass('lint-php: немає composer.json у корені — кроки PHP пропущено')
|
|
75
76
|
return reporter.getExitCode()
|
package/rules/php/meta.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{ "auto": { "glob": "composer.json" } }
|
|
1
|
+
{ "auto": { "glob": "composer.json" }, "lint": "full" }
|
package/rules/rust/js/lint.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { existsSync } from 'node:fs'
|
|
|
4
4
|
import { join } from 'node:path'
|
|
5
5
|
|
|
6
6
|
import { createCheckReporter } from '../../../scripts/lib/check-reporter.mjs'
|
|
7
|
+
import { runStandardLint } from '../../../scripts/lib/run-standard-lint.mjs'
|
|
7
8
|
import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -36,7 +37,7 @@ function runCargo(label, cargo, args, pass, fail) {
|
|
|
36
37
|
* @param {{ readOnly?: boolean }} [opts] readOnly → без мутацій
|
|
37
38
|
* @returns {number} exit code
|
|
38
39
|
*/
|
|
39
|
-
|
|
40
|
+
function runRustLint(cwd = process.cwd(), opts = {}) {
|
|
40
41
|
const readOnly = opts.readOnly === true
|
|
41
42
|
const reporter = createCheckReporter()
|
|
42
43
|
const { pass, fail } = reporter
|
|
@@ -65,3 +66,14 @@ export function lint(_files, cwd = process.cwd(), opts = {}) {
|
|
|
65
66
|
runCargo('cargo clippy -D warnings', cargo, ['clippy', '--all-targets', '--all-features', '--', '-D', 'warnings'], pass, fail)
|
|
66
67
|
return reporter.getExitCode()
|
|
67
68
|
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Locked orchestration entry point for `n-cursor lint rust`.
|
|
72
|
+
* @param {string[] | undefined} _files ігнорується (cargo обходить crate сам)
|
|
73
|
+
* @param {string} [cwd] корінь
|
|
74
|
+
* @param {{ readOnly?: boolean }} [opts] readOnly → без мутацій
|
|
75
|
+
* @returns {Promise<number>} exit code
|
|
76
|
+
*/
|
|
77
|
+
export function lint(_files, cwd = process.cwd(), opts = {}) {
|
|
78
|
+
return runStandardLint(import.meta.dirname, () => runRustLint(cwd, opts))
|
|
79
|
+
}
|
package/rules/rust/meta.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{ "auto": { "glob": "**/Cargo.toml" } }
|
|
1
|
+
{ "auto": { "glob": "**/Cargo.toml" }, "lint": "full" }
|