@7n/rules 1.8.3 → 1.8.4

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
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.8.4] - 2026-07-17
4
+
5
+ ### Fixed
6
+
7
+ - text/run-v8r: включати причину провалу v8r (stdout+stderr, без noise) у violation-повідомлення замість голого коду виходу
8
+ - k8s/manifests: форматування tests/check-schema.test.mjs (oxfmt)
9
+
3
10
  ## [1.8.3] - 2026-07-17
4
11
 
5
12
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7n/rules",
3
- "version": "1.8.3",
3
+ "version": "1.8.4",
4
4
  "description": "CLI еталонних правил і skills (префікс n-): синк у репозиторій, дельта-lint, конформність",
5
5
  "keywords": [
6
6
  "cli",
@@ -3,7 +3,8 @@ type: JS Module
3
3
  title: main.mjs
4
4
  resource: npm/rules/text/run-v8r/main.mjs
5
5
  docgen:
6
- crc: 804dce1c
6
+ crc: 5c8aab7b
7
+ model: manual
7
8
  ---
8
9
 
9
10
  ## Огляд
@@ -20,18 +21,20 @@ docgen:
20
21
  - `V8R_CACHE_TTL_SECONDS` — TTL HTTP-кешу v8r (доба замість дефолтних 600 с): fallback-фетчі schemastore не повторюються на кожен прогін.
21
22
  - `resolveCustomCatalogSchemas()` — читає джерельний каталог, повертає масив схем у форматі v8r `customCatalog.schemas` (ключ `location`, локальні шляхи — абсолютні).
22
23
  - `writeResolvedV8rConfig()` — записує `{ cacheTtl, customCatalog: { schemas } }` у `RESOLVED_V8R_CONFIG_PATH` (побічний ефект: файлова операція запису, поза rollback-механізмом лінт-пайплайна).
23
- - `runV8rWithGlobs(globs?)` — послідовно запускає `v8r` для кожного glob-у з `V8R_CONFIG_FILE`, вказаним на резольвнутий конфіг; при коді 0/98 вивід приховано, інакше друкується й повертається код першого невдалого прогону.
24
- - `runV8rWithFiles(files)` — delta-режим: один запуск `v8r` по конкретних існуючих шляхах (код 98 неможливий), порожній список — одразу 0.
24
+ - `runV8rWithGlobs(globs?)` — послідовно запускає `v8r` для кожного glob-у з `V8R_CONFIG_FILE`, вказаним на резольвнутий конфіг; повертає `{ code, detail }` (не голий код): при `code` 0/98 вивід приховано, `detail` порожній; інакше `detail` друкується й повертається код першого невдалого прогону.
25
+ - `runV8rWithFiles(files)` — delta-режим: один запуск `v8r` по конкретних існуючих шляхах (код 98 неможливий), порожній список — одразу `{ code: 0, detail: '' }`.
26
+ - `extractFailureLines(combinedText)` — фільтрує `ℹ`-шум і bunx install-вивід з об'єднаного `stdout+stderr` одного запуску `v8r`, лишаючи предметну деталь (`✖ …`-заголовки, ajv-причини). Обидва потоки об'єднуються навмисно: `v8r` непослідовно розкидає деталь між ними залежно від типу помилки (schema violation — причина в stdout, заголовок у stderr; "не знайдено схему" — усе в stderr).
25
27
  - `warnAboutRemoteSchemaFallback(stderrText)` — парсить stderr `v8r` і на кожен файл, чию схему знайдено мережевим fallback-ом (schemastore, не customCatalog), друкує в stdout пораду додати схему в каталог.
26
28
  - `stripBunNodeShimDirs(pathValue)` — прибирає з PATH shim-теки `bun-node-*`, які `bun run --bun` додає з підміненим `node`: дочірній `v8r` (node-shebang) інакше виконувався б під bun і падав на непідтримуваному `node:sea`. Дочірній процес `v8r` завжди отримує очищений PATH.
27
- - `lint(ctx)` — detector `text/run-v8r`: `ctx.files` → delta по відфільтрованих розширеннях, без `ctx.files` → full за `DEFAULT_V8R_GLOBS`.
29
+ - `lint(ctx)` — detector `text/run-v8r`: `ctx.files` → delta по відфільтрованих розширеннях, без `ctx.files` → full за `DEFAULT_V8R_GLOBS`. Порушення несе `detail` (якщо є) у тексті `fail()`-повідомлення — без нього LLM fix-worker бачив лише "щось не пройшло" й незмінно провалював усі rung-и драбини (не мав інформації, що саме виправляти).
28
30
 
29
31
  ## Публічний API
30
32
 
31
33
  - `DEFAULT_V8R_GLOBS`, `V8R_CATALOG_PATH`, `RESOLVED_V8R_CONFIG_PATH`, `V8R_CACHE_TTL_SECONDS` — константи.
32
34
  - `resolveCustomCatalogSchemas()` — обчислені схеми customCatalog.
33
35
  - `writeResolvedV8rConfig()` — матеріалізація тимчасового конфігу, повертає його шлях.
34
- - `runV8rWithGlobs(globs?)`, `runV8rWithFiles(files)` — точки входу перевірки (full/delta).
36
+ - `runV8rWithGlobs(globs?)`, `runV8rWithFiles(files)` — точки входу перевірки (full/delta), повертають `{ code, detail }`.
37
+ - `extractFailureLines(combinedText)` — фільтр noise-рядків для деталі провалу.
35
38
  - `warnAboutRemoteSchemaFallback(stderrText)` — попередження про мережевий fallback схем.
36
39
  - `stripBunNodeShimDirs(pathValue)` — PATH без shim-тек `bun-node-*` (для дочірнього v8r).
37
40
  - `lint(ctx)` — інтеграція в lint-пайплайн.
@@ -114,17 +114,28 @@ export function writeResolvedV8rConfig() {
114
114
 
115
115
  const PROCESSING_LINE_RE = /^ℹ Processing (.+)$/u
116
116
  const FOUND_REMOTE_SCHEMA_RE = /^ℹ Found schema in (https?:\/\/\S+)/u
117
- const FAILURE_LINE_RE = /^✖ .+$/mu
117
+ const NOISE_LINE_RE = /^(?:ℹ .*|Resolving dependencies|Resolved, downloaded and extracted.*|Saved lockfile)$/u
118
118
 
119
119
  /**
120
- * Витягує лише рядки `✖ …` (конкретні помилки валідації) з raw stdout v8r — без `ℹ`-шуму
121
- * (Pre-warming the cache, Processing <file>, Found schema in …) і без `✔ … is valid` по
122
- * пройдених файлах. Використовується для non-verbose підсумку при невдалому прогоні.
123
- * @param {string} stdoutText захоплений stdout одного запуску v8r
124
- * @returns {string} рядки `✖ …`, з'єднані `\n` (порожній рядок, якщо збігів нема)
120
+ * Прибирає v8r/bunx noise-рядки (весь `ℹ`-статус: Loaded config file, Patterns and relative
121
+ * paths, Pre-warming the cache, Processing <file>, Found schema in …, Validating …; і службовий
122
+ * вивід bunx-встановлення Resolving/Resolved/Saved lockfile) з об'єднаного stdout+stderr одного
123
+ * запуску
124
+ * лишає предметну деталь (`✖ …`-заголовки й ajv-причини на кшталт "must NOT have additional
125
+ * properties…"). Обидва потоки об'єднуються НАВМИСНО: v8r непослідовно розкидає ці рядки між
126
+ * stdout/stderr залежно від типу помилки — при порушенні схеми ajv-причина йде у stdout, а
127
+ * `✖ … is invalid`-заголовок у stderr; при "не знайдено схему" все йде у stderr, stdout
128
+ * порожній. Фільтр лише за stdout (як раніше) на другому випадку повертав би зовсім порожню
129
+ * деталь — і LLM fix-worker (як і non-verbose CLI-підсумок) не бачив би жодної причини провалу.
130
+ * @param {string} combinedText stdout + '\n' + stderr одного запуску v8r
131
+ * @returns {string} відфільтровані непорожні рядки, join('\n') (порожній рядок, якщо деталі нема)
125
132
  */
126
- export function extractFailureLines(stdoutText) {
127
- return (stdoutText.match(FAILURE_LINE_RE) ?? []).join('\n')
133
+ export function extractFailureLines(combinedText) {
134
+ return combinedText
135
+ .split('\n')
136
+ .map(line => line.trim())
137
+ .filter(line => line.length > 0 && !NOISE_LINE_RE.test(line))
138
+ .join('\n')
128
139
  }
129
140
 
130
141
  /**
@@ -168,11 +179,16 @@ export function stripBunNodeShimDirs(pathValue) {
168
179
 
169
180
  /**
170
181
  * Один виклик `bun x v8r <targets...>` з підготовленим `customCatalog`-конфігом.
182
+ * `detail` (рядки `✖ …`) обчислюється завжди, незалежно від `verbose` — потрібен викликачу
183
+ * (`lint()`) для вбудовування у violation-повідомлення, яке бачить LLM fix-worker: без нього
184
+ * fix-ladder отримує лише "щось не пройшло" й не має шансів вгадати, що саме (спостережено —
185
+ * усі 4 rung-и незмінно падають у timeout на v8r-порушеннях).
171
186
  * @param {string[]} targets glob-и або конкретні шляхи файлів
172
187
  * @param {string} configPath шлях до `V8R_CONFIG_FILE`
173
188
  * @param {boolean} [verbose] друкувати повний raw stdout/stderr v8r при помилці; інакше — лише
174
189
  * рядки `✖ …` без `ℹ`-шуму (Pre-warming the cache, Processing <file>, Found schema in …)
175
- * @returns {{ exitError: true } | { exitError: false, code: number }} помилка spawn або код v8r (0/98 — трактує викликач)
190
+ * @returns {{ exitError: true } | { exitError: false, code: number, detail: string }} помилка
191
+ * spawn або код v8r (0/98 — трактує викликач) + деталь `✖ …`-рядків
176
192
  */
177
193
  function runOneV8rInvocation(targets, configPath, verbose = false) {
178
194
  const bunPath = resolveCmd('bun') ?? process.execPath
@@ -192,16 +208,17 @@ function runOneV8rInvocation(targets, configPath, verbose = false) {
192
208
  warnAboutRemoteSchemaFallback(result.stderr ?? '')
193
209
 
194
210
  const exitCode = result.status ?? 1
211
+ let detail = ''
195
212
  if (exitCode !== 0 && exitCode !== 98) {
213
+ detail = extractFailureLines(`${result.stdout ?? ''}\n${result.stderr ?? ''}`)
196
214
  if (verbose) {
197
215
  if (result.stdout?.length) process.stdout.write(result.stdout)
198
216
  if (result.stderr?.length) process.stderr.write(result.stderr)
199
- } else {
200
- const failureLines = extractFailureLines(result.stdout ?? '')
201
- if (failureLines.length) process.stdout.write(`${failureLines}\n`)
217
+ } else if (detail.length) {
218
+ process.stdout.write(`${detail}\n`)
202
219
  }
203
220
  }
204
- return { exitError: false, code: exitCode }
221
+ return { exitError: false, code: exitCode, detail }
205
222
  }
206
223
 
207
224
  /**
@@ -210,24 +227,25 @@ function runOneV8rInvocation(targets, configPath, verbose = false) {
210
227
  * glob не знаходить файлів, і тоді решта розширень не перевіряються в тому ж виклику.
211
228
  * @param {string[]} [globs] патерни; за замовчуванням DEFAULT_V8R_GLOBS
212
229
  * @param {boolean} [verbose] друкувати повний raw вивід v8r при помилці (див. runOneV8rInvocation)
213
- * @returns {number} 0 — OK, 1 — помилка spawn, 2 — немає каталогу схем, інше — код v8r
230
+ * @returns {{ code: number, detail: string }} `code`: 0 — OK, 1 — помилка spawn, 2 — немає
231
+ * каталогу схем, інше — код v8r; `detail` — рядки `✖ …` (порожньо, якщо `code` не про валідацію)
214
232
  */
215
233
  export function runV8rWithGlobs(globs = DEFAULT_V8R_GLOBS, verbose = false) {
216
234
  if (!existsSync(V8R_CATALOG_PATH)) {
217
235
  process.stderr.write(
218
236
  `run-v8r: не знайдено каталог схем за шляхом ${V8R_CATALOG_PATH} (очікується npm/schemas/v8r-catalog.json у пакеті)\n`
219
237
  )
220
- return 2
238
+ return { code: 2, detail: '' }
221
239
  }
222
240
 
223
241
  const configPath = writeResolvedV8rConfig()
224
242
 
225
243
  for (const pattern of globs) {
226
244
  const r = runOneV8rInvocation([pattern], configPath, verbose)
227
- if (r.exitError) return 1
228
- if (r.code !== 0 && r.code !== 98) return r.code
245
+ if (r.exitError) return { code: 1, detail: '' }
246
+ if (r.code !== 0 && r.code !== 98) return { code: r.code, detail: r.detail }
229
247
  }
230
- return 0
248
+ return { code: 0, detail: '' }
231
249
  }
232
250
 
233
251
  /**
@@ -235,21 +253,33 @@ export function runV8rWithGlobs(globs = DEFAULT_V8R_GLOBS, verbose = false) {
235
253
  * бо кожен переданий шлях уже існує (не glob), тож код 98 "порожній glob" тут не виникає.
236
254
  * @param {string[]} files абсолютні або відносні до cwd v8r-процесу шляхи файлів
237
255
  * @param {boolean} [verbose] друкувати повний raw вивід v8r при помилці (див. runOneV8rInvocation)
238
- * @returns {number} 0 — OK, 1 — помилка spawn, 2 — немає каталогу схем, інше — код v8r
256
+ * @returns {{ code: number, detail: string }} `code`: 0 — OK, 1 — помилка spawn, 2 — немає
257
+ * каталогу схем, інше — код v8r; `detail` — рядки `✖ …` (порожньо, якщо `code` не про валідацію)
239
258
  */
240
259
  export function runV8rWithFiles(files, verbose = false) {
241
- if (files.length === 0) return 0
260
+ if (files.length === 0) return { code: 0, detail: '' }
242
261
  if (!existsSync(V8R_CATALOG_PATH)) {
243
262
  process.stderr.write(
244
263
  `run-v8r: не знайдено каталог схем за шляхом ${V8R_CATALOG_PATH} (очікується npm/schemas/v8r-catalog.json у пакеті)\n`
245
264
  )
246
- return 2
265
+ return { code: 2, detail: '' }
247
266
  }
248
267
 
249
268
  const configPath = writeResolvedV8rConfig()
250
269
  const r = runOneV8rInvocation(files, configPath, verbose)
251
- if (r.exitError) return 1
252
- return r.code === 98 ? 0 : r.code
270
+ if (r.exitError) return { code: 1, detail: '' }
271
+ return { code: r.code === 98 ? 0 : r.code, detail: r.detail }
272
+ }
273
+
274
+ /**
275
+ * Будує violation-повідомлення з опційною деталлю `✖ …`-рядків v8r — без неї LLM fix-worker
276
+ * бачить лише "щось не пройшло" й не має інформації, який файл/поле саме порушує схему.
277
+ * @param {string} detail рядки `✖ …` з `runV8rWithGlobs`/`runV8rWithFiles` (може бути порожнім)
278
+ * @returns {string} повне повідомлення для `fail()`
279
+ */
280
+ function v8rFailMessage(detail) {
281
+ const base = 'v8r schema-валідація json/yaml/toml не пройшла (text.mdc)'
282
+ return detail ? `${base}:\n${detail}` : base
253
283
  }
254
284
 
255
285
  /**
@@ -264,19 +294,19 @@ export function lint(ctx) {
264
294
  const verbose = ctx.verbose === true
265
295
 
266
296
  if (ctx.files === undefined) {
267
- const code = runV8rWithGlobs(DEFAULT_V8R_GLOBS, verbose)
268
- if (code !== 0) fail('v8r schema-валідація json/yaml/toml не пройшла (text.mdc)', 'v8r')
297
+ const { code, detail } = runV8rWithGlobs(DEFAULT_V8R_GLOBS, verbose)
298
+ if (code !== 0) fail(v8rFailMessage(detail), 'v8r')
269
299
  return reporter.result()
270
300
  }
271
301
 
272
302
  const files = ctx.files.filter(f => V8R_EXT_RE.test(f))
273
303
  if (files.length === 0) return reporter.result()
274
- const code = runV8rWithFiles(files, verbose)
275
- if (code !== 0) fail('v8r schema-валідація json/yaml/toml не пройшла (text.mdc)', 'v8r')
304
+ const { code, detail } = runV8rWithFiles(files, verbose)
305
+ if (code !== 0) fail(v8rFailMessage(detail), 'v8r')
276
306
  return reporter.result()
277
307
  }
278
308
 
279
309
  if (isRunAsCli(import.meta.url)) {
280
310
  const globs = process.argv.length > 2 ? process.argv.slice(2) : DEFAULT_V8R_GLOBS
281
- process.exitCode = runV8rWithGlobs(globs)
311
+ process.exitCode = runV8rWithGlobs(globs).code
282
312
  }