@nitra/cursor 1.8.179 → 1.8.184
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 +44 -5
- package/mdc/ga.mdc +23 -1
- package/mdc/js-run.mdc +35 -1
- package/package.json +4 -4
- package/scripts/check-abie.mjs +1 -0
- package/scripts/check-changelog.mjs +49 -50
- package/scripts/check-docker.mjs +1 -1
- package/scripts/check-ga.mjs +69 -8
- package/scripts/check-graphql.mjs +1 -0
- package/scripts/check-hasura.mjs +1 -0
- package/scripts/check-image.mjs +11 -7
- package/scripts/check-js-bun-db.mjs +3 -22
- package/scripts/check-js-lint.mjs +3 -1
- package/scripts/check-js-mssql.mjs +5 -23
- package/scripts/check-js-run.mjs +63 -3
- package/scripts/check-k8s.mjs +33 -32
- package/scripts/check-nginx-default-tpl.mjs +3 -0
- package/scripts/check-npm-module.mjs +1 -0
- package/scripts/check-vue.mjs +17 -10
- package/scripts/claude-stop-hook.mjs +24 -21
- package/scripts/lint-ga.mjs +0 -1
- package/scripts/rename-yaml-extensions.mjs +1 -0
- package/scripts/run-docker.mjs +1 -0
- package/scripts/run-k8s.mjs +1 -0
- package/scripts/sync-claude-config.mjs +28 -28
- package/scripts/utils/ast-scan-utils.mjs +1 -1
- package/scripts/utils/bun-sql-scan.mjs +1 -2
- package/scripts/utils/depcheck-workflow.mjs +188 -0
- package/scripts/utils/find-package-json-paths.mjs +30 -0
- package/scripts/utils/load-cursor-config.mjs +3 -1
- package/scripts/utils/oxlint-canonical.json +3 -16
- package/scripts/utils/walkDir.mjs +11 -9
package/scripts/check-image.mjs
CHANGED
|
@@ -187,9 +187,7 @@ async function checkLegacyCacheRemoved(pass, fail) {
|
|
|
187
187
|
}
|
|
188
188
|
const lines = await readGitignoreLines()
|
|
189
189
|
if (lines && lines.includes(LEGACY_CACHE_FILENAME)) {
|
|
190
|
-
fail(
|
|
191
|
-
`.gitignore: прибери застарілий рядок \`${LEGACY_CACHE_FILENAME}\` — split-cache 3.2.0 його не використовує`
|
|
192
|
-
)
|
|
190
|
+
fail(`.gitignore: прибери застарілий рядок \`${LEGACY_CACHE_FILENAME}\` — split-cache 3.2.0 його не використовує`)
|
|
193
191
|
return
|
|
194
192
|
}
|
|
195
193
|
pass(`${LEGACY_CACHE_FILENAME} відсутній (міграція на split-cache завершена)`)
|
|
@@ -203,7 +201,9 @@ async function checkLegacyCacheRemoved(pass, fail) {
|
|
|
203
201
|
*/
|
|
204
202
|
function packageHasAvifDisabled(pkg) {
|
|
205
203
|
const cfg = pkg[PKG_CONFIG_FIELD]
|
|
206
|
-
return Boolean(
|
|
204
|
+
return Boolean(
|
|
205
|
+
cfg && typeof cfg === 'object' && /** @type {Record<string, unknown>} */ (cfg)['disable-avif'] === true
|
|
206
|
+
)
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
/**
|
|
@@ -213,9 +213,10 @@ function packageHasAvifDisabled(pkg) {
|
|
|
213
213
|
* один раз (інакше при обході кореня `.` ми б повторно зайшли в `demo/` і подвоїли звіти).
|
|
214
214
|
* @param {string} packageRoot відносний шлях до кореня пакета (наприклад `'.'` або `'demo'`)
|
|
215
215
|
* @param {string[]} otherRootsAbs абсолютні шляхи інших workspace-коренів — їх піддерева пропускаємо
|
|
216
|
+
* @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
|
|
216
217
|
* @param {(msg: string) => void} pass callback при успішній перевірці
|
|
217
218
|
* @param {(msg: string) => void} fail callback при помилці
|
|
218
|
-
* @returns {Promise<void>}
|
|
219
|
+
* @returns {Promise<void>} резолвиться по завершенню перевірки одного пакета
|
|
219
220
|
*/
|
|
220
221
|
async function checkVueAvifImportsInPackage(packageRoot, otherRootsAbs, ignorePaths, pass, fail) {
|
|
221
222
|
const absRoot = join(process.cwd(), packageRoot)
|
|
@@ -263,9 +264,10 @@ async function checkVueAvifImportsInPackage(packageRoot, otherRootsAbs, ignorePa
|
|
|
263
264
|
* Сканує всі workspace-пакети: для кожного перевіряє opt-out і за потреби викликає
|
|
264
265
|
* перевірку Vue-imports. Перевірка пропускається, якщо в репозиторії немає workspaces
|
|
265
266
|
* або немає `.vue`-файлів — тоді `image` правило не для цього проєкту.
|
|
267
|
+
* @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
|
|
266
268
|
* @param {(msg: string) => void} pass callback при успішній перевірці
|
|
267
269
|
* @param {(msg: string) => void} fail callback при помилці
|
|
268
|
-
* @returns {Promise<void>}
|
|
270
|
+
* @returns {Promise<void>} резолвиться по завершенню перевірки всіх workspace-пакетів
|
|
269
271
|
*/
|
|
270
272
|
async function checkVueAvifImports(ignorePaths, pass, fail) {
|
|
271
273
|
const roots = await getMonorepoPackageRootDirs()
|
|
@@ -275,7 +277,9 @@ async function checkVueAvifImports(ignorePaths, pass, fail) {
|
|
|
275
277
|
if (!existsSync(pkgPath)) continue
|
|
276
278
|
const pkg = JSON.parse(await readFile(pkgPath, 'utf8'))
|
|
277
279
|
if (packageHasAvifDisabled(pkg)) {
|
|
278
|
-
pass(
|
|
280
|
+
pass(
|
|
281
|
+
`[${root === '.' ? 'корінь' : root}] avif-import enforcement вимкнено через "@nitra/minify-image.disable-avif"`
|
|
282
|
+
)
|
|
279
283
|
continue
|
|
280
284
|
}
|
|
281
285
|
const otherRootsAbs = roots.filter(r => r !== root && r !== '.').map(r => absRootsByRel.get(r) ?? '')
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
import { existsSync } from 'node:fs'
|
|
25
25
|
import { readFile } from 'node:fs/promises'
|
|
26
|
-
import { join, relative
|
|
26
|
+
import { join, relative } from 'node:path'
|
|
27
27
|
|
|
28
28
|
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
29
29
|
import {
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
isBunSqlScanSourceFile,
|
|
36
36
|
textHasBunSqlImport
|
|
37
37
|
} from './utils/bun-sql-scan.mjs'
|
|
38
|
+
import { findAllPackageJsonPaths } from './utils/find-package-json-paths.mjs'
|
|
38
39
|
import { loadCursorIgnorePaths } from './utils/load-cursor-config.mjs'
|
|
39
40
|
import { walkDir } from './utils/walkDir.mjs'
|
|
40
41
|
|
|
@@ -50,30 +51,10 @@ function asObject(v) {
|
|
|
50
51
|
return /** @type {Record<string, unknown>} */ (v)
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
/**
|
|
54
|
-
* Знаходить всі `package.json` у репозиторії (крім пропущених директорій у walkDir).
|
|
55
|
-
* @param {string} repoRoot абсолютний шлях до кореня репозиторію
|
|
56
|
-
* @returns {Promise<string[]>} абсолютні шляхи, відсортовані за відносним шляхом
|
|
57
|
-
*/
|
|
58
|
-
async function findAllPackageJsonPaths(repoRoot, ignorePaths) {
|
|
59
|
-
/** @type {string[]} */
|
|
60
|
-
const paths = []
|
|
61
|
-
await walkDir(
|
|
62
|
-
repoRoot,
|
|
63
|
-
absPath => {
|
|
64
|
-
if (absPath.endsWith(`${sep}package.json`)) {
|
|
65
|
-
paths.push(absPath)
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
ignorePaths
|
|
69
|
-
)
|
|
70
|
-
paths.sort((a, b) => relative(repoRoot, a).localeCompare(relative(repoRoot, b)))
|
|
71
|
-
return paths
|
|
72
|
-
}
|
|
73
|
-
|
|
74
54
|
/**
|
|
75
55
|
* Збирає абсолютні шляхи JS/TS джерел у репозиторії для скану Bun SQL патернів.
|
|
76
56
|
* @param {string} repoRoot абсолютний шлях до кореня репозиторію
|
|
57
|
+
* @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
|
|
77
58
|
* @returns {Promise<string[]>} абсолютні шляхи, відсортовані за відносним шляхом
|
|
78
59
|
*/
|
|
79
60
|
async function findAllSourcePathsForBunSqlScan(repoRoot, ignorePaths) {
|
|
@@ -145,7 +145,9 @@ function compareOxlintIgnorePatterns(expected, actual, failures) {
|
|
|
145
145
|
return
|
|
146
146
|
}
|
|
147
147
|
if (!Array.isArray(actual)) {
|
|
148
|
-
failures.push(
|
|
148
|
+
failures.push(
|
|
149
|
+
'.oxlintrc.json: поле "ignorePatterns" має бути масивом (канон задає мінімум, додаткові патерни дозволені)'
|
|
150
|
+
)
|
|
149
151
|
return
|
|
150
152
|
}
|
|
151
153
|
const set = new Set(actual)
|
|
@@ -10,9 +10,10 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { existsSync } from 'node:fs'
|
|
12
12
|
import { readFile } from 'node:fs/promises'
|
|
13
|
-
import { join, relative
|
|
13
|
+
import { join, relative } from 'node:path'
|
|
14
14
|
|
|
15
15
|
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
16
|
+
import { findAllPackageJsonPaths } from './utils/find-package-json-paths.mjs'
|
|
16
17
|
import {
|
|
17
18
|
findMssqlPerRequestConnectionInText,
|
|
18
19
|
findSharedMssqlRequestInText,
|
|
@@ -31,30 +32,10 @@ const SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)/u
|
|
|
31
32
|
/** Мінімальна дозволена версія mssql (js-mssql.mdc). */
|
|
32
33
|
const MIN_MSSQL_VERSION = { major: 12, minor: 5, patch: 0 }
|
|
33
34
|
|
|
34
|
-
/**
|
|
35
|
-
* Знаходить всі `package.json` у репозиторії (крім пропущених директорій у walkDir).
|
|
36
|
-
* @param {string} repoRoot абсолютний шлях до кореня репозиторію
|
|
37
|
-
* @returns {Promise<string[]>} абсолютні шляхи, відсортовані за відносним шляхом
|
|
38
|
-
*/
|
|
39
|
-
async function findAllPackageJsonPaths(repoRoot, ignorePaths) {
|
|
40
|
-
/** @type {string[]} */
|
|
41
|
-
const paths = []
|
|
42
|
-
await walkDir(
|
|
43
|
-
repoRoot,
|
|
44
|
-
absPath => {
|
|
45
|
-
if (absPath.endsWith(`${sep}package.json`)) {
|
|
46
|
-
paths.push(absPath)
|
|
47
|
-
}
|
|
48
|
-
},
|
|
49
|
-
ignorePaths
|
|
50
|
-
)
|
|
51
|
-
paths.sort((a, b) => relative(repoRoot, a).localeCompare(relative(repoRoot, b)))
|
|
52
|
-
return paths
|
|
53
|
-
}
|
|
54
|
-
|
|
55
35
|
/**
|
|
56
36
|
* Збирає абсолютні шляхи JS/TS джерел у репозиторії для скану mssql.
|
|
57
37
|
* @param {string} repoRoot абсолютний шлях до кореня репозиторію
|
|
38
|
+
* @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
|
|
58
39
|
* @returns {Promise<string[]>} абсолютні шляхи, відсортовані за відносним шляхом
|
|
59
40
|
*/
|
|
60
41
|
async function findAllSourcePathsForMssqlScan(repoRoot, ignorePaths) {
|
|
@@ -258,9 +239,10 @@ function reportZeroMssqlSourceViolations(counters, pass) {
|
|
|
258
239
|
/**
|
|
259
240
|
* Аудит усіх JS/TS-джерел репо щодо безпечного використання mssql.
|
|
260
241
|
* @param {string} repoRoot корінь репозиторію
|
|
242
|
+
* @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
|
|
261
243
|
* @param {(msg: string) => void} pass pass callback
|
|
262
244
|
* @param {(msg: string) => void} fail fail callback
|
|
263
|
-
* @returns {Promise<void>}
|
|
245
|
+
* @returns {Promise<void>} резолвиться по завершенню аудиту всіх знайдених джерел
|
|
264
246
|
*/
|
|
265
247
|
async function auditMssqlSources(repoRoot, ignorePaths, pass, fail) {
|
|
266
248
|
const sourcePaths = await findAllSourcePathsForMssqlScan(repoRoot, ignorePaths)
|
package/scripts/check-js-run.mjs
CHANGED
|
@@ -17,7 +17,11 @@
|
|
|
17
17
|
* `node:process` (для опційних). Коли `env` імпортовано з `@nitra/check-env`,
|
|
18
18
|
* кожен `env.X` має бути закритий літеральним викликом `checkEnv(['X', ...])`
|
|
19
19
|
* у тому ж файлі або коментарем `// \@nitra/cursor ignore-next-line checkEnv`
|
|
20
|
-
* на попередньому рядку (див. `utils/check-env-scan.mjs`)
|
|
20
|
+
* на попередньому рядку (див. `utils/check-env-scan.mjs`);
|
|
21
|
+
* - «depcheck у GitHub Actions з path-фільтром»: для кожного workflow з `paths:`,
|
|
22
|
+
* обмеженим каталогом цього пакета (`<rootDir>/...`), має бути крок
|
|
23
|
+
* `npx depcheck --ignores="graphql,bun"` (плюс інші, за потреби) з
|
|
24
|
+
* `working-directory: <rootDir>` (див. `utils/depcheck-workflow.mjs`).
|
|
21
25
|
*/
|
|
22
26
|
import { existsSync } from 'node:fs'
|
|
23
27
|
import { readFile } from 'node:fs/promises'
|
|
@@ -30,6 +34,7 @@ import {
|
|
|
30
34
|
} from './utils/bunyan-imports.mjs'
|
|
31
35
|
import { findUncheckedProcessEnvInText, isCheckEnvScanSourceFile } from './utils/check-env-scan.mjs'
|
|
32
36
|
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
37
|
+
import { findDepcheckViolationsForPackage, readAllWorkflowFiles } from './utils/depcheck-workflow.mjs'
|
|
33
38
|
import {
|
|
34
39
|
findConnFactoryImportsInText,
|
|
35
40
|
isConnImportsScanSourceFile,
|
|
@@ -53,6 +58,7 @@ function relPosix(absPackageRoot, absPath) {
|
|
|
53
58
|
/**
|
|
54
59
|
* Сканує джерела пакета на заборонені імпорти `@nitra/bunyan` / `bunyan`.
|
|
55
60
|
* @param {string} absPackageRoot абсолютний шлях до кореня пакета
|
|
61
|
+
* @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
|
|
56
62
|
* @param {string} label префікс повідомлення `[<pkg>] `
|
|
57
63
|
* @param {(msg: string) => void} fail callback при помилці
|
|
58
64
|
* @returns {Promise<number>} кількість знайдених порушень
|
|
@@ -86,6 +92,7 @@ async function checkBunyanImports(absPackageRoot, ignorePaths, label, fail) {
|
|
|
86
92
|
/**
|
|
87
93
|
* Збирає всі JS/TS-файли пакета (без node_modules, dist тощо).
|
|
88
94
|
* @param {string} absPackageRoot абсолютний шлях до кореня пакета
|
|
95
|
+
* @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
|
|
89
96
|
* @returns {Promise<string[]>} абсолютні шляхи до файлів
|
|
90
97
|
*/
|
|
91
98
|
async function collectSourceFiles(absPackageRoot, ignorePaths) {
|
|
@@ -158,15 +165,26 @@ async function checkProcessEnvUsage(absPackageRoot, sourcePaths, label, fail) {
|
|
|
158
165
|
/**
|
|
159
166
|
* Перевіряє відповідність правилам js-run.mdc для одного workspace-пакета.
|
|
160
167
|
* @param {string} rootDir відносний шлях workspace (не `'.'`)
|
|
168
|
+
* @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
|
|
169
|
+
* @param {{ relPath: string, content: string }[]} workflows кешований список workflow-файлів репо
|
|
161
170
|
* @param {(msg: string) => void} fail функція зворотного виклику для реєстрації помилки перевірки
|
|
162
171
|
* @param {(msg: string) => void} passFn успішне повідомлення (як у check-reporter)
|
|
163
172
|
* @returns {Promise<void>} завершується після перевірок цього пакета
|
|
164
173
|
*/
|
|
165
|
-
async function checkWorkspacePackage(rootDir, ignorePaths, fail, passFn) {
|
|
174
|
+
async function checkWorkspacePackage(rootDir, ignorePaths, workflows, fail, passFn) {
|
|
166
175
|
const label = `[${rootDir}] `
|
|
167
176
|
const absPackageRoot = join(process.cwd(), rootDir)
|
|
168
177
|
const pkgJson = await loadPackageJsonAndCheckBunyanDeps(rootDir, label, fail)
|
|
169
178
|
|
|
179
|
+
// Frontend-пакети (vite у devDependencies) виходять за межі js-run:
|
|
180
|
+
// браузерний бандл не має `node:process`, а `process.env.*` бандлер
|
|
181
|
+
// обробляє самостійно. Перевірку process.env / conn-аліасів пропускаємо,
|
|
182
|
+
// bunyan-залежність уже звірено в `loadPackageJsonAndCheckBunyanDeps`.
|
|
183
|
+
if (packageJsonHasViteDevDependency(pkgJson)) {
|
|
184
|
+
passFn(`${label}vite-пакет (frontend) — js-run пропущено (process.env / conn-aliases / OTEL configmap)`)
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
|
|
170
188
|
const importViolations = await checkBunyanImports(absPackageRoot, ignorePaths, label, fail)
|
|
171
189
|
if (importViolations === 0) {
|
|
172
190
|
passFn(`${label}немає імпортів '@nitra/bunyan' / 'bunyan' у джерелах`)
|
|
@@ -188,6 +206,47 @@ async function checkWorkspacePackage(rootDir, ignorePaths, fail, passFn) {
|
|
|
188
206
|
}
|
|
189
207
|
|
|
190
208
|
await checkOtelConfigmap(rootDir, label, fail, passFn)
|
|
209
|
+
|
|
210
|
+
checkDepcheckInWorkflows(rootDir, workflows, label, fail, passFn)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Перевіряє правило «depcheck у workflow» для одного пакета.
|
|
215
|
+
*
|
|
216
|
+
* Для кожного `.github/workflows/*.yml`, чий `paths:` обмежено до `<rootDir>/...`,
|
|
217
|
+
* має бути крок `npx depcheck --ignores="graphql,bun"` з `working-directory: <rootDir>`.
|
|
218
|
+
* Якщо в репо немає каталогу `.github/workflows`, перевірка no-op.
|
|
219
|
+
* @param {string} rootDir відносний шлях workspace-пакета
|
|
220
|
+
* @param {{ relPath: string, content: string }[]} workflows кешований список workflow-файлів
|
|
221
|
+
* @param {string} label префікс повідомлення `[<pkg>] `
|
|
222
|
+
* @param {(msg: string) => void} fail callback при помилці
|
|
223
|
+
* @param {(msg: string) => void} passFn успішне повідомлення
|
|
224
|
+
* @returns {void}
|
|
225
|
+
*/
|
|
226
|
+
function checkDepcheckInWorkflows(rootDir, workflows, label, fail, passFn) {
|
|
227
|
+
if (workflows.length === 0) return
|
|
228
|
+
const violations = findDepcheckViolationsForPackage(workflows, rootDir.replace(/\\/g, '/'))
|
|
229
|
+
if (violations.length === 0) {
|
|
230
|
+
passFn(`${label}depcheck у path-scoped workflow налаштовано (або відсутній path-scoped workflow для пакета)`)
|
|
231
|
+
return
|
|
232
|
+
}
|
|
233
|
+
for (const v of violations) {
|
|
234
|
+
fail(`${label}${v}`)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Чи має пакет `vite` у `devDependencies` (маркер frontend-пакета — vite/quasar/capacitor SPA).
|
|
240
|
+
* Семантично ідентично `packageJsonLacksViteDevDependency` з `auto-rules.mjs`, але
|
|
241
|
+
* приймає вже розпарсений pkgJson.
|
|
242
|
+
* @param {unknown} pkgJson розпарсений `package.json` пакета (або null)
|
|
243
|
+
* @returns {boolean} true, якщо `vite` присутній у `devDependencies`
|
|
244
|
+
*/
|
|
245
|
+
function packageJsonHasViteDevDependency(pkgJson) {
|
|
246
|
+
if (!pkgJson || typeof pkgJson !== 'object' || Array.isArray(pkgJson)) return false
|
|
247
|
+
const devDeps = /** @type {Record<string, unknown>} */ (pkgJson).devDependencies
|
|
248
|
+
if (!devDeps || typeof devDeps !== 'object' || Array.isArray(devDeps)) return false
|
|
249
|
+
return Object.hasOwn(devDeps, 'vite')
|
|
191
250
|
}
|
|
192
251
|
|
|
193
252
|
/**
|
|
@@ -255,8 +314,9 @@ export async function check() {
|
|
|
255
314
|
}
|
|
256
315
|
|
|
257
316
|
const ignorePaths = await loadCursorIgnorePaths(process.cwd())
|
|
317
|
+
const workflows = await readAllWorkflowFiles(process.cwd())
|
|
258
318
|
for (const r of workspaceRoots) {
|
|
259
|
-
await checkWorkspacePackage(r, ignorePaths, fail, pass)
|
|
319
|
+
await checkWorkspacePackage(r, ignorePaths, workflows, fail, pass)
|
|
260
320
|
}
|
|
261
321
|
|
|
262
322
|
return reporter.getExitCode()
|
package/scripts/check-k8s.mjs
CHANGED
|
@@ -1339,43 +1339,44 @@ function failIfExplicitPatchTargetsNotInCatalog(rel, first, catalog, fail) {
|
|
|
1339
1339
|
* @returns {void}
|
|
1340
1340
|
*/
|
|
1341
1341
|
function failIfExplicitPatchTargetsHaveRedundantGroupVersion(rel, first, catalog, fail) {
|
|
1342
|
-
for (const
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
}
|
|
1346
|
-
const t = /** @type {Record<string, unknown>} */ (target)
|
|
1347
|
-
const kind = typeof t.kind === 'string' ? t.kind.trim() : ''
|
|
1348
|
-
const name = typeof t.name === 'string' ? t.name.trim() : ''
|
|
1349
|
-
if (kind === '' || name === '') {
|
|
1350
|
-
continue
|
|
1351
|
-
}
|
|
1352
|
-
if (patchTargetUsesSelector(t)) {
|
|
1353
|
-
continue
|
|
1354
|
-
}
|
|
1355
|
-
const tgtGroup = typeof t.group === 'string' ? t.group.trim() : ''
|
|
1356
|
-
const tgtVersion = typeof t.version === 'string' ? t.version.trim() : ''
|
|
1357
|
-
if (tgtGroup === '' && tgtVersion === '') {
|
|
1358
|
-
continue
|
|
1359
|
-
}
|
|
1360
|
-
const matchingByKindName = catalog.filter(r => r.kind === kind && r.name === name)
|
|
1361
|
-
const distinctGvk = new Set(matchingByKindName.map(r => `${r.group}/${r.version}`))
|
|
1362
|
-
if (distinctGvk.size > 1) {
|
|
1363
|
-
continue
|
|
1364
|
-
}
|
|
1365
|
-
/** @type {string[]} */
|
|
1366
|
-
const redundant = []
|
|
1367
|
-
if (tgtGroup !== '') {
|
|
1368
|
-
redundant.push('group')
|
|
1369
|
-
}
|
|
1370
|
-
if (tgtVersion !== '') {
|
|
1371
|
-
redundant.push('version')
|
|
1372
|
-
}
|
|
1342
|
+
for (const entry of extractExplicitPatchTargetsFromKustomization(first)) {
|
|
1343
|
+
const violation = describePatchTargetRedundancy(entry, catalog)
|
|
1344
|
+
if (violation === null) continue
|
|
1345
|
+
const { section, index, kind, name, redundant } = violation
|
|
1373
1346
|
fail(
|
|
1374
1347
|
`${rel}: ${section}[${index}].target — прибери зайві поля ${redundant.join(', ')}; для kind=${kind}, name=${name} в інвентарі немає колізії між різними API-групами/версіями (див. k8s.mdc «patches[].target: лише kind і name»)`
|
|
1375
1348
|
)
|
|
1376
1349
|
}
|
|
1377
1350
|
}
|
|
1378
1351
|
|
|
1352
|
+
/**
|
|
1353
|
+
* Аналізує один patch.target: повертає опис надлишкових полів `group`/`version`,
|
|
1354
|
+
* якщо в інвентарі для пари (kind, name) немає колізії GVK; інакше `null`.
|
|
1355
|
+
* @param {{ section: string, index: number, target: unknown }} entry елемент із `extractExplicitPatchTargetsFromKustomization`
|
|
1356
|
+
* @param {KustomizeResourceDescriptor[]} catalog інвентар resources/bases/…
|
|
1357
|
+
* @returns {{ section: string, index: number, kind: string, name: string, redundant: string[] } | null} опис порушення або `null`
|
|
1358
|
+
*/
|
|
1359
|
+
function describePatchTargetRedundancy(entry, catalog) {
|
|
1360
|
+
const { section, index, target } = entry
|
|
1361
|
+
if (target === null || typeof target !== 'object' || Array.isArray(target)) return null
|
|
1362
|
+
const t = /** @type {Record<string, unknown>} */ (target)
|
|
1363
|
+
const kind = typeof t.kind === 'string' ? t.kind.trim() : ''
|
|
1364
|
+
const name = typeof t.name === 'string' ? t.name.trim() : ''
|
|
1365
|
+
if (kind === '' || name === '') return null
|
|
1366
|
+
if (patchTargetUsesSelector(t)) return null
|
|
1367
|
+
const tgtGroup = typeof t.group === 'string' ? t.group.trim() : ''
|
|
1368
|
+
const tgtVersion = typeof t.version === 'string' ? t.version.trim() : ''
|
|
1369
|
+
if (tgtGroup === '' && tgtVersion === '') return null
|
|
1370
|
+
const matchingByKindName = catalog.filter(r => r.kind === kind && r.name === name)
|
|
1371
|
+
const distinctGvk = new Set(matchingByKindName.map(r => `${r.group}/${r.version}`))
|
|
1372
|
+
if (distinctGvk.size > 1) return null
|
|
1373
|
+
/** @type {string[]} */
|
|
1374
|
+
const redundant = []
|
|
1375
|
+
if (tgtGroup !== '') redundant.push('group')
|
|
1376
|
+
if (tgtVersion !== '') redundant.push('version')
|
|
1377
|
+
return { section, index, kind, name, redundant }
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1379
1380
|
/**
|
|
1380
1381
|
* Документи з YAML-файлу мають мати дескриптор у **catalog** (інвентар resources).
|
|
1381
1382
|
* @param {string} rel відносний шлях до kustomization.yaml
|
|
@@ -1636,7 +1637,7 @@ export function baseKustomizationNamespaceViolation(obj) {
|
|
|
1636
1637
|
/**
|
|
1637
1638
|
* Збирає всі `*.yaml` та `*.yml` під деревом від кореня cwd, якщо шлях містить сегмент `k8s` (для `.yml` далі — помилка перейменування).
|
|
1638
1639
|
* @param {string} root корінь репозиторію (cwd)
|
|
1639
|
-
* @param {string[]} [ignorePaths
|
|
1640
|
+
* @param {string[]} [ignorePaths] шляхи каталогів, повністю виключених з обходу
|
|
1640
1641
|
* @returns {Promise<string[]>} відсортовані абсолютні шляхи до файлів
|
|
1641
1642
|
*/
|
|
1642
1643
|
async function findK8sYamlFiles(root, ignorePaths = []) {
|
|
@@ -35,6 +35,7 @@ const GZIP_EXTENSION_RE = /\*\.(?:js|css)/u
|
|
|
35
35
|
/**
|
|
36
36
|
* Збирає абсолютні шляхи до **default.conf.template** у репозиторії; шлях `tests/fixtures` не обходиться як проєктний шаблон.
|
|
37
37
|
* @param {string} root корінь cwd
|
|
38
|
+
* @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
|
|
38
39
|
* @returns {Promise<string[]>} відсортовані абсолютні шляхи до шаблонів
|
|
39
40
|
*/
|
|
40
41
|
export async function findDefaultConfTemplatePaths(root, ignorePaths = []) {
|
|
@@ -57,6 +58,7 @@ export async function findDefaultConfTemplatePaths(root, ignorePaths = []) {
|
|
|
57
58
|
* Знаходить у дереві від `root` усі **default.tpl.conf**. Якщо поруч немає **default.conf.template** —
|
|
58
59
|
* перейменовує файл; якщо є — перезаписує **default.conf.template** вмістом **default.tpl.conf** і видаляє **default.tpl.conf**.
|
|
59
60
|
* @param {string} root корінь обходу (зазвичай cwd репозиторію)
|
|
61
|
+
* @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
|
|
60
62
|
* @returns {Promise<{ renamed: string[], overwritten: string[] }>} відносні шляхи до обробленого **default.tpl.conf** (для звіту)
|
|
61
63
|
*/
|
|
62
64
|
export async function migrateDefaultTplConfFiles(root, ignorePaths = []) {
|
|
@@ -322,6 +324,7 @@ async function checkTemplateFile(abs, root, passFn, failFn) {
|
|
|
322
324
|
/**
|
|
323
325
|
* Перевіряє Dockerfile на наявність gzip та envsubst.
|
|
324
326
|
* @param {string} root корінь репозиторію
|
|
327
|
+
* @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
|
|
325
328
|
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
326
329
|
* @param {(msg: string) => void} failFn callback при помилці
|
|
327
330
|
*/
|
|
@@ -36,6 +36,7 @@ const EMIT_TYPES_CONFIG = 'npm/tsconfig.emit-types.json'
|
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* Чи є під `npm/src` хоча б один `.js` (рекурсивно).
|
|
39
|
+
* @param {string[]} [ignorePaths] абсолютні шляхи каталогів, повністю виключених з обходу
|
|
39
40
|
* @returns {Promise<boolean>} `true`, якщо знайдено хоча б один `.js`
|
|
40
41
|
*/
|
|
41
42
|
async function npmSrcTreeHasJsFile(ignorePaths = []) {
|
package/scripts/check-vue.mjs
CHANGED
|
@@ -115,6 +115,7 @@ async function collectEsbuildMatchesInFiles(absPackageRoot, files, maxMatches) {
|
|
|
115
115
|
* Сканує дерево пакета на згадки `esbuild` і підказує заміну на `rolldown`.
|
|
116
116
|
* @param {string} rootDir відносний шлях до пакета
|
|
117
117
|
* @param {string} absPackageRoot абсолютний шлях до кореня пакета
|
|
118
|
+
* @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
|
|
118
119
|
* @param {string} prefix параметр prefix
|
|
119
120
|
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
120
121
|
* @param {(msg: string) => void} fail callback при помилці
|
|
@@ -342,9 +343,7 @@ async function checkVueNodeImportViolations(rootDir, absPackageRoot, ignorePaths
|
|
|
342
343
|
}
|
|
343
344
|
}
|
|
344
345
|
if (nodeImportViolations === 0) {
|
|
345
|
-
passFn(
|
|
346
|
-
`${prefix}немає імпортів Node-нативних модулів у .vue (проскановано ${ukFilesCountPhrase(vuePaths.length)})`
|
|
347
|
-
)
|
|
346
|
+
passFn(`${prefix}немає імпортів Node-нативних модулів у .vue (проскановано ${ukFilesCountPhrase(vuePaths.length)})`)
|
|
348
347
|
}
|
|
349
348
|
}
|
|
350
349
|
|
|
@@ -354,18 +353,17 @@ async function checkVueNodeImportViolations(rootDir, absPackageRoot, ignorePaths
|
|
|
354
353
|
* Якщо `unplugin-auto-import` не сконфігурований на `'vue'` у `vite.config`, явні value-імпорти
|
|
355
354
|
* формально не заборонені — їх видалення зламає код. У цьому випадку перевірка пропускається,
|
|
356
355
|
* а fail про відсутній `'vue'` у `AutoImport.imports` уже зареєстровано в `checkViteConfig`.
|
|
357
|
-
* @param {string} rootDir
|
|
358
|
-
* @param {string} absPackageRoot
|
|
359
|
-
* @param {string}
|
|
356
|
+
* @param {string} rootDir відносний шлях до пакета
|
|
357
|
+
* @param {string} absPackageRoot абсолютний шлях до кореня пакета
|
|
358
|
+
* @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
|
|
360
359
|
* @param {boolean} hasVueAutoImport чи `AutoImport({ imports: [..., 'vue', ...] })` сконфігуровано
|
|
360
|
+
* @param {string} prefix префікс повідомлення `[<pkg>] `
|
|
361
361
|
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
362
362
|
* @param {(msg: string) => void} fail callback при помилці
|
|
363
363
|
*/
|
|
364
364
|
async function checkVueImportViolations(rootDir, absPackageRoot, ignorePaths, hasVueAutoImport, prefix, passFn, fail) {
|
|
365
365
|
if (!hasVueAutoImport) {
|
|
366
|
-
passFn(
|
|
367
|
-
`${prefix}value-імпорти з 'vue' не заборонені — спершу додай 'vue' до AutoImport.imports у vite.config`
|
|
368
|
-
)
|
|
366
|
+
passFn(`${prefix}value-імпорти з 'vue' не заборонені — спершу додай 'vue' до AutoImport.imports у vite.config`)
|
|
369
367
|
return
|
|
370
368
|
}
|
|
371
369
|
/** @type {string[]} */
|
|
@@ -400,6 +398,7 @@ async function checkVueImportViolations(rootDir, absPackageRoot, ignorePaths, ha
|
|
|
400
398
|
/**
|
|
401
399
|
* Перевіряє залежності та vite.config одного Vue-пакета.
|
|
402
400
|
* @param {string} rootDir відносний шлях до пакета
|
|
401
|
+
* @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
|
|
403
402
|
* @param {(msg: string) => void} fail функція зворотного виклику для реєстрації помилки перевірки
|
|
404
403
|
* @param {(msg: string) => void} passFn успішне повідомлення (як у check-reporter)
|
|
405
404
|
* @returns {Promise<void>} завершується після перевірок залежностей, `vite.config` і сканування джерел на імпорти з `vue`
|
|
@@ -444,7 +443,15 @@ async function checkVuePackage(rootDir, ignorePaths, fail, passFn) {
|
|
|
444
443
|
)
|
|
445
444
|
|
|
446
445
|
const { hasVueAutoImport } = await checkViteConfig(rootDir, prefix, passFn, fail)
|
|
447
|
-
await checkVueImportViolations(
|
|
446
|
+
await checkVueImportViolations(
|
|
447
|
+
rootDir,
|
|
448
|
+
join(process.cwd(), rootDir),
|
|
449
|
+
ignorePaths,
|
|
450
|
+
hasVueAutoImport,
|
|
451
|
+
prefix,
|
|
452
|
+
passFn,
|
|
453
|
+
fail
|
|
454
|
+
)
|
|
448
455
|
await checkVueNodeImportViolations(rootDir, join(process.cwd(), rootDir), ignorePaths, prefix, passFn, fail)
|
|
449
456
|
await checkEsbuildMentions(rootDir, join(process.cwd(), rootDir), ignorePaths, prefix, passFn, fail)
|
|
450
457
|
}
|
|
@@ -11,25 +11,27 @@
|
|
|
11
11
|
* `npx --no @nitra/cursor stop-hook`
|
|
12
12
|
*/
|
|
13
13
|
import { spawn } from 'node:child_process'
|
|
14
|
+
import { once } from 'node:events'
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Зчитує stdin до EOF як utf8 рядок. Якщо stdin порожній (TTY) — повертає '' одразу.
|
|
17
18
|
* @returns {Promise<string>} вміст stdin
|
|
18
19
|
*/
|
|
19
|
-
function readStdin() {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
process.stdin.on('data', chunk => {
|
|
28
|
-
data += chunk
|
|
29
|
-
})
|
|
30
|
-
process.stdin.on('end', () => resolve(data))
|
|
31
|
-
process.stdin.on('error', () => resolve(data))
|
|
20
|
+
async function readStdin() {
|
|
21
|
+
if (process.stdin.isTTY) {
|
|
22
|
+
return ''
|
|
23
|
+
}
|
|
24
|
+
process.stdin.setEncoding('utf8')
|
|
25
|
+
const chunks = []
|
|
26
|
+
process.stdin.on('data', chunk => {
|
|
27
|
+
chunks.push(chunk)
|
|
32
28
|
})
|
|
29
|
+
try {
|
|
30
|
+
await once(process.stdin, 'end')
|
|
31
|
+
} catch {
|
|
32
|
+
// 'error' на stdin — повертаємо те, що встигли зібрати
|
|
33
|
+
}
|
|
34
|
+
return chunks.join('')
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
/**
|
|
@@ -60,12 +62,13 @@ export async function runStopHookCli() {
|
|
|
60
62
|
if (isRecursiveStopHookCall(stdin)) {
|
|
61
63
|
return 0
|
|
62
64
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
child
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
})
|
|
70
|
-
|
|
65
|
+
// eslint-disable-next-line sonarjs/no-os-command-from-path -- npx як стандартне dev-середовище через PATH; альтернативи (хардкод шляху) непортативні
|
|
66
|
+
const child = spawn('npx', ['--no', '@nitra/cursor', 'check'], { stdio: 'inherit' })
|
|
67
|
+
try {
|
|
68
|
+
const [code] = await once(child, 'exit')
|
|
69
|
+
return code ?? 1
|
|
70
|
+
} catch (error) {
|
|
71
|
+
process.stderr.write(`stop-hook: не вдалося запустити npx @nitra/cursor check — ${error.message}\n`)
|
|
72
|
+
return 1
|
|
73
|
+
}
|
|
71
74
|
}
|
package/scripts/lint-ga.mjs
CHANGED
|
@@ -18,7 +18,6 @@ import { resolveCmd } from './utils/resolve-cmd.mjs'
|
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Опис залежності preflight-ом: бінарник, для чого потрібен, і команди встановлення.
|
|
21
|
-
*
|
|
22
21
|
* @typedef {object} PreflightDep
|
|
23
22
|
* @property {string} bin ім'я виконуваного файлу (на Windows додається `.exe` за потреби)
|
|
24
23
|
* @property {string[]} winBins альтернативні імена на Windows (`shellcheck.exe`); якщо нема — fallback на `bin`
|
|
@@ -68,6 +68,7 @@ export function replaceExtension(relPosix, newExt) {
|
|
|
68
68
|
/**
|
|
69
69
|
* Збирає операції перейменування (без виконання).
|
|
70
70
|
* @param {string} rootAbs абсолютний корінь репозиторію
|
|
71
|
+
* @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
|
|
71
72
|
* @returns {Promise<Array<{ kind: 'k8s' | 'github', fromAbs: string, toAbs: string, relFrom: string, relTo: string }>>} відсортовані операції перейменування без запису на диск
|
|
72
73
|
*/
|
|
73
74
|
async function collectRenameOps(rootAbs, ignorePaths) {
|
package/scripts/run-docker.mjs
CHANGED
|
@@ -29,6 +29,7 @@ export function isLintDockerfileName(name) {
|
|
|
29
29
|
/**
|
|
30
30
|
* Збирає абсолютні шляхи для lint-docker.
|
|
31
31
|
* @param {string} root корінь репозиторію
|
|
32
|
+
* @param {string[]} [ignorePaths] абсолютні шляхи каталогів, повністю виключених з обходу
|
|
32
33
|
* @returns {Promise<string[]>} відсортовані абсолютні шляхи
|
|
33
34
|
*/
|
|
34
35
|
export async function findLintDockerfilePaths(root, ignorePaths = []) {
|
package/scripts/run-k8s.mjs
CHANGED
|
@@ -59,6 +59,7 @@ export function k8sRootFromFile(absFile) {
|
|
|
59
59
|
/**
|
|
60
60
|
* Унікальні корені `k8s` за наявності `*.yaml` під деревом cwd.
|
|
61
61
|
* @param {string} root корінь репозиторію
|
|
62
|
+
* @param {string[]} [ignorePaths] абсолютні шляхи каталогів, повністю виключених з обходу
|
|
62
63
|
* @returns {Promise<string[]>} відсортовані абсолютні шляхи до каталогів `k8s`
|
|
63
64
|
*/
|
|
64
65
|
export async function findK8sRoots(root, ignorePaths = []) {
|