@nitra/cursor 1.8.219 → 1.8.221
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 +12 -0
- package/bin/n-cursor.js +25 -4
- package/mdc/ci4.mdc +51 -0
- package/mdc/k8s.mdc +2 -0
- package/package.json +1 -1
- package/scripts/auto-skills.mjs +8 -1
- package/scripts/check-bun.mjs +3 -3
- package/scripts/check-changelog.mjs +2 -3
- package/scripts/check-image-avif.mjs +14 -6
- package/scripts/check-image-compress.mjs +1 -1
- package/scripts/check-js-run.mjs +58 -47
- package/scripts/check-k8s.mjs +141 -51
- package/scripts/check-npm-module.mjs +1 -4
- package/scripts/check-php.mjs +5 -5
- package/scripts/claude-stop-hook.mjs +2 -2
- package/scripts/lint-conftest.mjs +15 -7
- package/scripts/lint-ga.mjs +1 -1
- package/scripts/run-shellcheck-text.mjs +94 -64
- package/scripts/sync-claude-config.mjs +1 -1
- package/scripts/utils/ast-scan-utils.mjs +28 -0
- package/scripts/utils/bun-sql-scan.mjs +53 -34
- package/scripts/utils/bunyan-imports.mjs +10 -61
- package/scripts/utils/conn-file-rules.mjs +76 -37
- package/scripts/utils/depcheck-workflow.mjs +27 -6
- package/scripts/utils/redis-imports.mjs +9 -51
- package/skills/llm-patch/SKILL.md +16 -5
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,18 @@
|
|
|
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.221] - 2026-05-10
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- **ci4.mdc:** наповнено правило людинозрозумілим описом C4-моделі як джерела істини. Markdown-файли (C4 + ADR + тести + документація) — офіційне джерело істини про проєкт. Перед змінами агент аналізує відповідні C4-файли; кожна зміна, що впливає на модель, супроводжується оновленням C4-схеми у тому ж PR. ADR описує вплив рішення на C4 (які контейнери/компоненти з'являються/зникають/змінюють відповідальність). Кожен C4-компонент має посилання на відповідні тести. C4-схеми — частина користувацької документації, не закритий артефакт. Алгоритмічної `check-ci4.mjs` поки немає — правило процесне; `## Перевірка` залишено для майбутньої формалізації.
|
|
12
|
+
|
|
13
|
+
## [1.8.220] - 2026-05-09
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **k8s / `prodOverlayHpaPdbOverrideNeeds`:** виключено Kustomize Component (`kind: Component`) з prod-overlay-перевірки. Раніше `<pkg>/k8s/components/kustomization.yaml` помилково тригерив `прод-оверлей має перевизначати spec.minReplicas/maxReplicas/minAvailable` — але Component є **джерелом** ресурсів для overlays, не overlay сам по собі. Прод-перезаписи живуть у `ru/` / `ua/` / `prod/` тощо, що підключають Component через `components:`. Додано ранній return за `kind: Component` у `npm/scripts/check-k8s.mjs`; уточнення додано до `npm/mdc/k8s.mdc` і регресійний тест у `npm/tests/check-k8s-schema.test.mjs`.
|
|
18
|
+
|
|
7
19
|
## [1.8.219] - 2026-05-09
|
|
8
20
|
|
|
9
21
|
### Added
|
package/bin/n-cursor.js
CHANGED
|
@@ -1137,7 +1137,8 @@ async function readBundledVersionAt(packageRoot) {
|
|
|
1137
1137
|
* бо ES-модулі вже завантажені у V8 (RULE_MIGRATIONS, detectAutoRules тощо) і нова логіка
|
|
1138
1138
|
* без повної заміни процесу не підхопиться. Захист від нескінченного циклу — env `NITRA_CURSOR_REEXEC=1`.
|
|
1139
1139
|
* @param {string} effectivePackageRoot шлях, повернутий `upgradeNitraCursorToLatestAndBunInstall`
|
|
1140
|
-
* @returns {Promise<void>} повертається лише якщо re-exec не
|
|
1140
|
+
* @returns {Promise<void>} повертається лише якщо re-exec не потрібен; інакше кидає `ReexecHandoff`,
|
|
1141
|
+
* який ловить top-level catch і прокидає exit-код у `process.exitCode`
|
|
1141
1142
|
*/
|
|
1142
1143
|
async function reexecIfPackageVersionChanged(effectivePackageRoot) {
|
|
1143
1144
|
if (env.NITRA_CURSOR_REEXEC === '1') {
|
|
@@ -1167,7 +1168,23 @@ async function reexecIfPackageVersionChanged(effectivePackageRoot) {
|
|
|
1167
1168
|
if (result.error) {
|
|
1168
1169
|
throw result.error
|
|
1169
1170
|
}
|
|
1170
|
-
|
|
1171
|
+
throw new ReexecHandoff(typeof result.status === 'number' ? result.status : 1)
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
/**
|
|
1175
|
+
* Сентинельна помилка, яку кидає `reexecIfPackageVersionChanged` після успішного re-exec.
|
|
1176
|
+
* Top-level catch розпізнає її й виставляє `process.exitCode = code` без stack-trace —
|
|
1177
|
+
* процес тоді коректно завершується з тим самим кодом, що й child re-exec-у.
|
|
1178
|
+
*/
|
|
1179
|
+
class ReexecHandoff extends Error {
|
|
1180
|
+
/**
|
|
1181
|
+
* @param {number} code exit-код, який повернув child-процес
|
|
1182
|
+
*/
|
|
1183
|
+
constructor(code) {
|
|
1184
|
+
super('reexec-handoff')
|
|
1185
|
+
this.name = 'ReexecHandoff'
|
|
1186
|
+
this.code = code
|
|
1187
|
+
}
|
|
1171
1188
|
}
|
|
1172
1189
|
|
|
1173
1190
|
/**
|
|
@@ -1329,6 +1346,10 @@ try {
|
|
|
1329
1346
|
process.exitCode = 1
|
|
1330
1347
|
}
|
|
1331
1348
|
}
|
|
1332
|
-
} catch {
|
|
1333
|
-
|
|
1349
|
+
} catch (error) {
|
|
1350
|
+
if (error instanceof ReexecHandoff) {
|
|
1351
|
+
process.exitCode = error.code
|
|
1352
|
+
} else {
|
|
1353
|
+
process.exitCode = 1
|
|
1354
|
+
}
|
|
1334
1355
|
}
|
package/mdc/ci4.mdc
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Дизайн проєкту за C4 model (https://c4model.com); Markdown — джерело істини про архітектуру, рішення, тести й документацію
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
version: '1.0'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
C4-діаграми проєкту живуть у Markdown поряд із кодом. Це не довідник «для людей із порталу
|
|
8
|
+
архітектора» — це **джерело істини**, з якого LLM-агент і людина читають намір системи перед
|
|
9
|
+
будь-якою зміною коду. Тому правила нижче — не оформлення, а робочий процес.
|
|
10
|
+
|
|
11
|
+
## Markdown як джерело істини
|
|
12
|
+
|
|
13
|
+
Уся ключова інформація про проєкт — архітектура (C4), рішення (ADR), тести й документація —
|
|
14
|
+
зберігається у `.md`/`.mdc`-файлах і є **офіційним джерелом істини**. Це єдиний спосіб
|
|
15
|
+
тримати знання разом із кодом — версійно, в одному PR і доступно для агентів. Якщо щось
|
|
16
|
+
важливе про систему існує лише у Confluence/Notion/месенджері — для проєкту цього **немає**.
|
|
17
|
+
|
|
18
|
+
## Аналіз перед зміною
|
|
19
|
+
|
|
20
|
+
Перш ніж вносити зміни, агент **читає відповідні C4-файли**: контекст, контейнери, компоненти
|
|
21
|
+
тієї ділянки, до якої входить редагований код. Без цього кроку легко зламати приховані
|
|
22
|
+
залежності між контейнерами або «загубити» зовнішню інтеграцію.
|
|
23
|
+
|
|
24
|
+
## Оновлення синхронно зі змінами
|
|
25
|
+
|
|
26
|
+
Кожна зміна, що впливає на C4-модель — нова інтеграція, новий компонент, перейменування,
|
|
27
|
+
зміна напрямку залежності, видалення сервісу — **супроводжується оновленням C4-схеми у тому ж
|
|
28
|
+
PR**. C4 не оновлюється «потім» окремою задачею: розсинхрон між кодом і моделлю — найчастіша
|
|
29
|
+
причина, чому архітектурні документи перестають читати.
|
|
30
|
+
|
|
31
|
+
## Зв'язок із ADR
|
|
32
|
+
|
|
33
|
+
ADR (`docs/adr/`) описує **вплив рішення на C4**: які контейнери/компоненти з'являються,
|
|
34
|
+
зникають, змінюють відповідальність. Якщо рішення суттєве — у тілі ADR явно пишемо, які
|
|
35
|
+
C4-схеми потрібно оновити, і робимо це у тому ж PR.
|
|
36
|
+
|
|
37
|
+
## Зв'язок із тестами
|
|
38
|
+
|
|
39
|
+
Кожен C4-компонент має **посилання на відповідні тести** — інтеграційні, e2e або юніт залежно
|
|
40
|
+
від рівня. Так перехід «компонент → як його перевіряємо» займає один клік, а не пошук по
|
|
41
|
+
репозиторію.
|
|
42
|
+
|
|
43
|
+
## Зв'язок із документацією
|
|
44
|
+
|
|
45
|
+
C4-схеми — частина **користувацької документації**, а не закритий артефакт для команди.
|
|
46
|
+
Контекстна діаграма (рівень 1) і контейнерна (рівень 2) живуть там, де читач шукає вступ у
|
|
47
|
+
проєкт, а не у відокремленій теці «for-architects».
|
|
48
|
+
|
|
49
|
+
## Перевірка
|
|
50
|
+
|
|
51
|
+
`npx @nitra/cursor check ci4`
|
package/mdc/k8s.mdc
CHANGED
|
@@ -387,6 +387,8 @@ images:
|
|
|
387
387
|
|
|
388
388
|
**Overlays** (`ua/`, `ru/`, прод-overlays) підключають `components: [- ../components]` і додають JSON6902-патчі для прод-значень: `/spec/minReplicas`, `/spec/maxReplicas` (HPA), `/spec/minAvailable` (PDB). Dev-середовище (`base`) HPA/PDB не отримує — як і потрібно.
|
|
389
389
|
|
|
390
|
+
**`<pkg>/k8s/components/kustomization.yaml`** має `kind: Component` (не `kind: Kustomization`) — це **джерело** канонічних HPA/PDB для всіх overlays, а не overlay сам по собі. Прод-перезаписи (`/spec/minReplicas`, `/spec/maxReplicas`, `/spec/minAvailable`) живуть у `<env>/kustomization.yaml`, що підключає Component через `components:`. У самому Component patches не потрібні — він env-неутральний; **`check k8s`** не вимагає прод-патчів від `components/kustomization.yaml`.
|
|
391
|
+
|
|
390
392
|
У **не-base** оверлеях (без `components/`) поруч із `Deployment` лишається звична схема: окремий **`hpa.yaml`** / **`pdb.yaml`**, якщо такі потрібні для цього середовища. **`check k8s`** звіряє прив'язку за іменами:
|
|
391
393
|
|
|
392
394
|
- **`hpa.yaml`** (поза **`…/base/`**) — `autoscaling/v2`, `HorizontalPodAutoscaler`, `spec.scaleTargetRef.name` **= `metadata.name`** Deployment.
|
package/package.json
CHANGED
package/scripts/auto-skills.mjs
CHANGED
|
@@ -12,7 +12,14 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
/** Порядок автододавання skills відповідно до `auto-skills.md`. */
|
|
15
|
-
export const AUTO_SKILL_ORDER = Object.freeze([
|
|
15
|
+
export const AUTO_SKILL_ORDER = Object.freeze([
|
|
16
|
+
'abie-kustomize',
|
|
17
|
+
'fix',
|
|
18
|
+
'lint',
|
|
19
|
+
'llm-patch',
|
|
20
|
+
'publish-telegram',
|
|
21
|
+
'taze'
|
|
22
|
+
])
|
|
16
23
|
|
|
17
24
|
/**
|
|
18
25
|
* Залежність скілів від правил (`auto-skills.md` синтаксис `skill - [rules]`).
|
package/scripts/check-bun.mjs
CHANGED
|
@@ -104,10 +104,10 @@ export async function check() {
|
|
|
104
104
|
fail('Відсутній bun.lock — запусти bun i')
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
if (
|
|
108
|
-
fail('Відсутній bunfig.toml — створи з [install] linker = "hoisted" (bun.mdc)')
|
|
109
|
-
} else {
|
|
107
|
+
if (existsSync('bunfig.toml')) {
|
|
110
108
|
pass('bunfig.toml є (структуру перевіряє bun run lint-conftest → bun.bunfig)')
|
|
109
|
+
} else {
|
|
110
|
+
fail('Відсутній bunfig.toml — створи з [install] linker = "hoisted" (bun.mdc)')
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
const cursorRules = await loadNCursorRules()
|
|
@@ -160,9 +160,8 @@ async function readBaseVersion(baseRef, ws) {
|
|
|
160
160
|
* @returns {boolean} `true`, якщо запис для `version` знайдено
|
|
161
161
|
*/
|
|
162
162
|
function changelogHasVersionEntry(text, version) {
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
return re.test(text)
|
|
163
|
+
const needle = `## [${version}]`
|
|
164
|
+
return text.startsWith(needle) || text.includes(`\n${needle}`)
|
|
166
165
|
}
|
|
167
166
|
|
|
168
167
|
/**
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* ув'язування `.avif`-двійників з посиланнями у `.vue`/`.html`.
|
|
4
4
|
*
|
|
5
5
|
* Дії під час `check image-avif`:
|
|
6
|
-
* 1. `npx
|
|
6
|
+
* 1. `npx \@nitra/minify-image --src=. --write --avif` — генерує AVIF-двійники.
|
|
7
7
|
* 2. У кожному workspace-пакеті переписує raster-посилання у `.vue`/`.html` на `.avif`
|
|
8
|
-
* (де AVIF-двійник реально існує на диску). Pakety з `"
|
|
8
|
+
* (де AVIF-двійник реально існує на диску). Pakety з `"\@nitra/minify-image": {
|
|
9
9
|
* "disable-avif": true }` у `package.json` пропускаються.
|
|
10
10
|
* 3. Прибирає AVIF-сироти — `<name>.<ext>.avif`, на які не лишилось жодного посилання
|
|
11
11
|
* у `.vue`/`.html` репозиторію, видаляються (умова правила: «AVIF лишається лише
|
|
@@ -27,13 +27,14 @@ import { env } from 'node:process'
|
|
|
27
27
|
|
|
28
28
|
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
29
29
|
import { loadCursorIgnorePaths } from './utils/load-cursor-config.mjs'
|
|
30
|
+
import { resolveCmd } from './utils/resolve-cmd.mjs'
|
|
30
31
|
import { walkDir } from './utils/walkDir.mjs'
|
|
31
32
|
import { getMonorepoPackageRootDirs } from './utils/workspaces.mjs'
|
|
32
33
|
|
|
33
34
|
/** Імʼя CLI-пакета, який генерує AVIF. */
|
|
34
35
|
const MINIFY_PACKAGE_NAME = '@nitra/minify-image'
|
|
35
36
|
|
|
36
|
-
/** Поле в `package.json` для конфігу
|
|
37
|
+
/** Поле в `package.json` для конфігу `\@nitra/minify-image` (наприклад, `disable-avif`). */
|
|
37
38
|
const PKG_CONFIG_FIELD = '@nitra/minify-image'
|
|
38
39
|
|
|
39
40
|
/**
|
|
@@ -279,7 +280,7 @@ async function checkVueAvifImports(ignorePaths, usedAvifAbs, stats, pass, fail)
|
|
|
279
280
|
|
|
280
281
|
/**
|
|
281
282
|
* Чи є в репозиторії хоч один raster-файл, який мав би сенс конвертувати у AVIF.
|
|
282
|
-
* Якщо немає — `npx
|
|
283
|
+
* Якщо немає — `npx \@nitra/minify-image` нема що робити, тож зайвий запуск пропускаємо
|
|
283
284
|
* (важливо у тестах: фікстурні `.png`-імпорти посилаються на неіснуючі файли, тож
|
|
284
285
|
* minify-image все одно нічого не згенерує — а зайвий npx-спавн повільний і робить шум).
|
|
285
286
|
* @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
|
|
@@ -299,7 +300,7 @@ async function hasAnyRasterImage(ignorePaths) {
|
|
|
299
300
|
}
|
|
300
301
|
|
|
301
302
|
/**
|
|
302
|
-
* Запускає `npx
|
|
303
|
+
* Запускає `npx \@nitra/minify-image --src=. --write --avif` для генерації AVIF-двійників.
|
|
303
304
|
*
|
|
304
305
|
* Виклик best-effort: якщо мережа/кеш недоступні чи бінарника нема — лог-варн без падіння
|
|
305
306
|
* перевірки (валідації package.json і vue-refs все одно прогоняться, vue-refs на
|
|
@@ -309,7 +310,14 @@ async function hasAnyRasterImage(ignorePaths) {
|
|
|
309
310
|
*/
|
|
310
311
|
function runAvifGeneration() {
|
|
311
312
|
if (env.NITRA_CURSOR_NO_AVIF_RUN === '1') return
|
|
312
|
-
const
|
|
313
|
+
const npxPath = resolveCmd('npx')
|
|
314
|
+
if (!npxPath) {
|
|
315
|
+
console.log(
|
|
316
|
+
` ⚠️ 'npx' не знайдено в PATH — пропускаємо генерацію AVIF; vue/html-перевірка покаже файли, для яких не вистачає .avif`
|
|
317
|
+
)
|
|
318
|
+
return
|
|
319
|
+
}
|
|
320
|
+
const result = spawnSync(npxPath, [MINIFY_PACKAGE_NAME, '--src=.', '--write', '--avif'], {
|
|
313
321
|
stdio: 'inherit',
|
|
314
322
|
env
|
|
315
323
|
})
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* **Що покрила Rego** (`bun run lint-conftest`,
|
|
13
13
|
* `npm/policy/image_compress/package_json/`):
|
|
14
|
-
* - `scripts.lint-image` викликає `npx
|
|
14
|
+
* - `scripts.lint-image` викликає `npx \@nitra/minify-image --src=. --write`
|
|
15
15
|
* без `--avif` (AVIF — окреме правило `image-avif`);
|
|
16
16
|
* - агрегований `lint` (якщо є) містить `bun run lint-image`;
|
|
17
17
|
* - `@nitra/minify-image` НЕ у `dependencies` / `devDependencies` (через `npx`).
|
package/scripts/check-js-run.mjs
CHANGED
|
@@ -207,35 +207,56 @@ async function checkConnFileNamingAndExports(absPackageRoot, sourcePaths, pkgJso
|
|
|
207
207
|
let violations = 0
|
|
208
208
|
for (const absPath of sourcePaths) {
|
|
209
209
|
const rel = relPosix(absPackageRoot, absPath)
|
|
210
|
-
if (!
|
|
211
|
-
if (!isConnFileRulesSourceFile(rel)) continue
|
|
212
|
-
// пропускаємо реекспортний барель `index.*` (якщо знадобиться) і прихований .d.ts
|
|
213
|
-
const base = rel.slice(rel.lastIndexOf('/') + 1)
|
|
214
|
-
if (base.startsWith('index.')) continue
|
|
215
|
-
|
|
210
|
+
if (!isConnFileToCheck(rel, connDir)) continue
|
|
216
211
|
const content = await readFile(absPath, 'utf8')
|
|
217
212
|
for (const v of findConnFileRuleViolations(content, rel)) {
|
|
218
213
|
violations++
|
|
219
|
-
|
|
220
|
-
fail(
|
|
221
|
-
`${label}${rel} — назва файла в '${connDir}/' не відповідає канону js-run: ` +
|
|
222
|
-
`'ql-<id>', 'pg-{read|write}[-<id>]', 'mysql-{read|write}[-<id>]' або 'mssql-{read|write}[-<id>]' ` +
|
|
223
|
-
`(kebab-case, [a-z0-9-])`
|
|
224
|
-
)
|
|
225
|
-
} else if (v.kind === 'default-export') {
|
|
226
|
-
fail(`${label}${rel} — 'export default' заборонений у '${connDir}/'; зроби іменований експорт`)
|
|
227
|
-
} else {
|
|
228
|
-
const found = v.foundNames?.length ? v.foundNames.join(', ') : '—'
|
|
229
|
-
fail(
|
|
230
|
-
`${label}${rel} — очікується іменований експорт 'export const ${v.expectedName} = …' ` +
|
|
231
|
-
`(camelCase від назви файла); знайдено: ${found}`
|
|
232
|
-
)
|
|
233
|
-
}
|
|
214
|
+
fail(formatConnFileViolation(v, label, rel, connDir))
|
|
234
215
|
}
|
|
235
216
|
}
|
|
236
217
|
return violations
|
|
237
218
|
}
|
|
238
219
|
|
|
220
|
+
/**
|
|
221
|
+
* Чи `rel` — це conn-файл, який треба валідувати: під `connDir/`, з JS/TS-розширенням,
|
|
222
|
+
* не `index.*` (який є реекспортним барелем).
|
|
223
|
+
* @param {string} rel відносний шлях у posix-форматі
|
|
224
|
+
* @param {string} connDir каталог conn-файлів (наприклад `src/conn`)
|
|
225
|
+
* @returns {boolean} true, якщо файл потрібно перевірити
|
|
226
|
+
*/
|
|
227
|
+
function isConnFileToCheck(rel, connDir) {
|
|
228
|
+
if (!isInsideConnDir(rel, connDir)) return false
|
|
229
|
+
if (!isConnFileRulesSourceFile(rel)) return false
|
|
230
|
+
const base = rel.slice(rel.lastIndexOf('/') + 1)
|
|
231
|
+
return !base.startsWith('index.')
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Будує повідомлення про конкретне порушення canon-у файла з `connDir/`.
|
|
236
|
+
* @param {{ kind: 'name' | 'default-export' | 'export-name', expectedName?: string, foundNames?: string[] }} v опис порушення
|
|
237
|
+
* @param {string} label префікс повідомлення `[<pkg>] `
|
|
238
|
+
* @param {string} rel відносний шлях файла
|
|
239
|
+
* @param {string} connDir каталог conn-файлів
|
|
240
|
+
* @returns {string} повний текст повідомлення для `fail(...)`
|
|
241
|
+
*/
|
|
242
|
+
function formatConnFileViolation(v, label, rel, connDir) {
|
|
243
|
+
if (v.kind === 'name') {
|
|
244
|
+
return (
|
|
245
|
+
`${label}${rel} — назва файла в '${connDir}/' не відповідає канону js-run: ` +
|
|
246
|
+
`'ql-<id>', 'pg-{read|write}[-<id>]', 'mysql-{read|write}[-<id>]' або 'mssql-{read|write}[-<id>]' ` +
|
|
247
|
+
`(kebab-case, [a-z0-9-])`
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
if (v.kind === 'default-export') {
|
|
251
|
+
return `${label}${rel} — 'export default' заборонений у '${connDir}/'; зроби іменований експорт`
|
|
252
|
+
}
|
|
253
|
+
const found = v.foundNames?.length ? v.foundNames.join(', ') : '—'
|
|
254
|
+
return (
|
|
255
|
+
`${label}${rel} — очікується іменований експорт 'export const ${v.expectedName} = …' ` +
|
|
256
|
+
`(camelCase від назви файла); знайдено: ${found}`
|
|
257
|
+
)
|
|
258
|
+
}
|
|
259
|
+
|
|
239
260
|
/**
|
|
240
261
|
* Перевіряє правило «CheckEnv» для пакета.
|
|
241
262
|
* @param {string} absPackageRoot абсолютний корінь пакета
|
|
@@ -297,12 +318,12 @@ async function checkPromiseSetTimeoutPause(absPackageRoot, sourcePaths, label, f
|
|
|
297
318
|
async function checkWorkspacePackage(rootDir, ignorePaths, workflows, fail, passFn) {
|
|
298
319
|
const label = `[${rootDir}] `
|
|
299
320
|
const absPackageRoot = join(process.cwd(), rootDir)
|
|
300
|
-
const pkgJson = await
|
|
321
|
+
const pkgJson = await loadPackageJson(rootDir)
|
|
301
322
|
|
|
302
323
|
// Frontend-пакети (vite у devDependencies) виходять за межі js-run:
|
|
303
324
|
// браузерний бандл не має `node:process`, а `process.env.*` бандлер
|
|
304
|
-
// обробляє самостійно. Перевірку process.env / conn-аліасів
|
|
305
|
-
// bunyan-залежність
|
|
325
|
+
// обробляє самостійно. Перевірку process.env / conn-аліасів пропускаємо;
|
|
326
|
+
// bunyan-залежність валідується в Rego (`bun run lint-conftest`).
|
|
306
327
|
if (packageJsonHasViteDevDependency(pkgJson)) {
|
|
307
328
|
passFn(`${label}vite-пакет (frontend) — js-run пропущено (process.env / conn-aliases / OTEL configmap)`)
|
|
308
329
|
return
|
|
@@ -343,7 +364,7 @@ async function checkWorkspacePackage(rootDir, ignorePaths, workflows, fail, pass
|
|
|
343
364
|
passFn(`${label}немає 'new Promise(r => setTimeout(r, ms))' — паузи через 'node:timers/promises'`)
|
|
344
365
|
}
|
|
345
366
|
|
|
346
|
-
|
|
367
|
+
checkOtelConfigmap(rootDir, passFn)
|
|
347
368
|
|
|
348
369
|
checkDepcheckInWorkflows(rootDir, workflows, label, fail, passFn)
|
|
349
370
|
}
|
|
@@ -388,41 +409,31 @@ function packageJsonHasViteDevDependency(pkgJson) {
|
|
|
388
409
|
}
|
|
389
410
|
|
|
390
411
|
/**
|
|
391
|
-
* Завантажує `package.json` пакета (якщо є)
|
|
412
|
+
* Завантажує `package.json` пакета (якщо є). Заборону `@nitra/bunyan` / `bunyan`
|
|
413
|
+
* у dependencies/devDependencies перенесено в Rego (`npm/policy/js_run/package_json/`);
|
|
414
|
+
* `bun run lint-conftest` запускає її по всіх workspace `package.json`. Тут лишилася
|
|
415
|
+
* лише AST-перевірка імпортів.
|
|
392
416
|
* @param {string} rootDir відносний шлях workspace
|
|
393
|
-
* @param {string} label префікс повідомлення `[<pkg>] `
|
|
394
|
-
* @param {(msg: string) => void} fail callback при помилці
|
|
395
417
|
* @returns {Promise<unknown>} розпарсений package.json або null
|
|
396
418
|
*/
|
|
397
|
-
async function
|
|
419
|
+
async function loadPackageJson(rootDir) {
|
|
398
420
|
const pkgPath = join(rootDir, 'package.json')
|
|
399
421
|
if (!existsSync(pkgPath)) return null
|
|
400
|
-
|
|
401
|
-
// Заборону `@nitra/bunyan` / `bunyan` у dependencies/devDependencies перенесено
|
|
402
|
-
// в Rego (`npm/policy/js_run/package_json/`); `bun run lint-conftest` запускає
|
|
403
|
-
// її по всіх workspace `package.json`. Тут лишилася лише AST-перевірка імпортів.
|
|
404
|
-
void label
|
|
405
|
-
void fail
|
|
406
|
-
return pkgJson
|
|
422
|
+
return JSON.parse(await readFile(pkgPath, 'utf8'))
|
|
407
423
|
}
|
|
408
424
|
|
|
409
425
|
/**
|
|
410
|
-
* Перевіряє
|
|
411
|
-
* з обов'язковими `service.name=`
|
|
426
|
+
* Перевіряє наявність `k8s/base/configmap.yaml` пакета. Структуру (наявність
|
|
427
|
+
* `OTEL_RESOURCE_ATTRIBUTES` з обов'язковими `service.name=` / `service.namespace=`)
|
|
428
|
+
* перенесено в Rego (`npm/policy/js_run/configmap/`); `bun run lint-conftest`
|
|
429
|
+
* запускає її на всіх `k8s/base/configmap.yaml`.
|
|
412
430
|
* @param {string} rootDir відносний шлях workspace
|
|
413
|
-
* @param {string} label префікс повідомлення `[<pkg>] `
|
|
414
|
-
* @param {(msg: string) => void} fail callback при помилці
|
|
415
431
|
* @param {(msg: string) => void} passFn успішне повідомлення
|
|
416
|
-
* @returns {
|
|
432
|
+
* @returns {void}
|
|
417
433
|
*/
|
|
418
|
-
function checkOtelConfigmap(rootDir,
|
|
434
|
+
function checkOtelConfigmap(rootDir, passFn) {
|
|
419
435
|
const configmapPath = join(rootDir, 'k8s', 'base', 'configmap.yaml')
|
|
420
436
|
if (!existsSync(configmapPath)) return
|
|
421
|
-
// Перевірку `OTEL_RESOURCE_ATTRIBUTES` має містити `service.name=` /
|
|
422
|
-
// `service.namespace=` перенесено в Rego (`npm/policy/js_run/configmap/`);
|
|
423
|
-
// `bun run lint-conftest` запускає її на всіх `k8s/base/configmap.yaml`.
|
|
424
|
-
void label
|
|
425
|
-
void fail
|
|
426
437
|
passFn(`${rootDir}/k8s/base/configmap.yaml є (OTEL — bun run lint-conftest → js_run.configmap)`)
|
|
427
438
|
}
|
|
428
439
|
|