@nitra/cursor 1.8.144 → 1.8.147
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/bin/auto-rules.md +2 -0
- package/bin/n-cursor.js +1 -0
- package/bin/rename-yaml-extensions.mjs +0 -1
- package/mdc/js-bun-db.mdc +118 -0
- package/mdc/js-lint.mdc +6 -4
- package/mdc/js-mssql.mdc +27 -1
- package/mdc/k8s.mdc +2 -2
- package/mdc/vue.mdc +3 -3
- package/package.json +3 -3
- package/scripts/auto-rules.mjs +104 -26
- package/scripts/check-ga.mjs +27 -15
- package/scripts/check-js-bun-db.mjs +213 -0
- package/scripts/check-js-lint.mjs +110 -27
- package/scripts/check-js-mssql.mjs +156 -86
- package/scripts/check-k8s.mjs +161 -32
- package/scripts/check-nginx-default-tpl.mjs +1 -1
- package/scripts/check-vue.mjs +82 -45
- package/scripts/cli-entry.mjs +2 -5
- package/scripts/upgrade-nitra-cursor-and-install.mjs +1 -1
- package/scripts/utils/bun-sql-scan.mjs +294 -0
- package/scripts/utils/mssql-pool-scan.mjs +188 -1
- package/scripts/utils/oxlint-canonical-skeleton.json +27 -0
- package/scripts/utils/oxlint-canonical.json +387 -0
- package/scripts/utils/oxlint-rules.tsv +359 -0
- package/scripts/utils/rebuild-oxlint-canonical.mjs +29 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Перевіряє правило js-bun-db.mdc.
|
|
3
|
+
*
|
|
4
|
+
* 1) У жодному `package.json` (включно з workspace-пакетами) у `dependencies` не повинно
|
|
5
|
+
* бути `pg` чи `mysql2` — ці бібліотеки треба замінити на Bun native SQL
|
|
6
|
+
* (`import { sql, SQL } from 'bun'`, https://bun.com/docs/runtime/sql).
|
|
7
|
+
*
|
|
8
|
+
* 2) Якщо в коді використовується Bun SQL (імпорт `sql`/`SQL` з `'bun'`), додатково
|
|
9
|
+
* перевіряє небезпечні патерни:
|
|
10
|
+
* - `new SQL(...)` всередині функції (пул має бути singleton на рівні модуля).
|
|
11
|
+
* - `sql.unsafe(\`...${expr}...\`)` (інтерполяція даних у `unsafe` ламає параметризацію).
|
|
12
|
+
* - Динамічні SQL-списки через `.join(',')` у `IN (...)` / `VALUES (...)`
|
|
13
|
+
* (треба `sql([...])`).
|
|
14
|
+
*/
|
|
15
|
+
import { existsSync } from 'node:fs'
|
|
16
|
+
import { readFile } from 'node:fs/promises'
|
|
17
|
+
import { join, relative, sep } from 'node:path'
|
|
18
|
+
|
|
19
|
+
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
20
|
+
import {
|
|
21
|
+
findBunSqlPerRequestConnectionInText,
|
|
22
|
+
findUnsafeBunSqlDynamicSqlListInText,
|
|
23
|
+
findUnsafeBunSqlUnsafeCallInText,
|
|
24
|
+
isBunSqlScanSourceFile,
|
|
25
|
+
textHasBunSqlImport
|
|
26
|
+
} from './utils/bun-sql-scan.mjs'
|
|
27
|
+
import { walkDir } from './utils/walkDir.mjs'
|
|
28
|
+
|
|
29
|
+
/** Імена забороненої залежності у будь-якому `package.json`. */
|
|
30
|
+
const FORBIDDEN_DEPENDENCIES = Object.freeze(['pg', 'mysql2'])
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {unknown} v parsed JSON
|
|
34
|
+
* @returns {Record<string, unknown>} object або {}
|
|
35
|
+
*/
|
|
36
|
+
function asObject(v) {
|
|
37
|
+
if (!v || typeof v !== 'object' || Array.isArray(v)) return {}
|
|
38
|
+
return /** @type {Record<string, unknown>} */ (v)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Знаходить всі `package.json` у репозиторії (крім пропущених директорій у walkDir).
|
|
43
|
+
* @param {string} repoRoot абсолютний шлях до кореня репозиторію
|
|
44
|
+
* @returns {Promise<string[]>} абсолютні шляхи, відсортовані за відносним шляхом
|
|
45
|
+
*/
|
|
46
|
+
async function findAllPackageJsonPaths(repoRoot) {
|
|
47
|
+
/** @type {string[]} */
|
|
48
|
+
const paths = []
|
|
49
|
+
await walkDir(repoRoot, absPath => {
|
|
50
|
+
if (absPath.endsWith(`${sep}package.json`)) {
|
|
51
|
+
paths.push(absPath)
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
paths.sort((a, b) => relative(repoRoot, a).localeCompare(relative(repoRoot, b)))
|
|
55
|
+
return paths
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Збирає абсолютні шляхи JS/TS джерел у репозиторії для скану Bun SQL патернів.
|
|
60
|
+
* @param {string} repoRoot абсолютний шлях до кореня репозиторію
|
|
61
|
+
* @returns {Promise<string[]>} абсолютні шляхи, відсортовані за відносним шляхом
|
|
62
|
+
*/
|
|
63
|
+
async function findAllSourcePathsForBunSqlScan(repoRoot) {
|
|
64
|
+
/** @type {string[]} */
|
|
65
|
+
const paths = []
|
|
66
|
+
await walkDir(repoRoot, absPath => {
|
|
67
|
+
const rel = relative(repoRoot, absPath).split('\\').join('/')
|
|
68
|
+
if (isBunSqlScanSourceFile(rel)) {
|
|
69
|
+
paths.push(absPath)
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
paths.sort((a, b) => relative(repoRoot, a).localeCompare(relative(repoRoot, b)))
|
|
73
|
+
return paths
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Перевіряє, чи в кореневому `package.json` присутні заборонені пакети у `dependencies`.
|
|
78
|
+
* @param {string[]} pkgJsonPaths абсолютні шляхи всіх `package.json` у репо
|
|
79
|
+
* @param {string} repoRoot абсолютний шлях до кореня
|
|
80
|
+
* @param {{ pass: (m: string) => void, fail: (m: string) => void }} reporter колбеки pass і fail з перевірки
|
|
81
|
+
* @returns {Promise<number>} кількість знайдених порушень
|
|
82
|
+
*/
|
|
83
|
+
async function checkForbiddenDependencies(pkgJsonPaths, repoRoot, reporter) {
|
|
84
|
+
const { pass, fail } = reporter
|
|
85
|
+
let bad = 0
|
|
86
|
+
for (const absPath of pkgJsonPaths) {
|
|
87
|
+
const rel = relative(repoRoot, absPath).split('\\').join('/')
|
|
88
|
+
let parsed
|
|
89
|
+
try {
|
|
90
|
+
parsed = JSON.parse(await readFile(absPath, 'utf8'))
|
|
91
|
+
} catch {
|
|
92
|
+
fail(`js-bun-db: ${rel} — невалідний JSON`)
|
|
93
|
+
bad++
|
|
94
|
+
continue
|
|
95
|
+
}
|
|
96
|
+
const deps = asObject(parsed.dependencies)
|
|
97
|
+
for (const name of FORBIDDEN_DEPENDENCIES) {
|
|
98
|
+
if (Object.hasOwn(deps, name)) {
|
|
99
|
+
bad++
|
|
100
|
+
fail(
|
|
101
|
+
`js-bun-db: ${rel}: dependencies.${name} — замінити на Bun native SQL ` +
|
|
102
|
+
`(import { sql, SQL } from 'bun', https://bun.com/docs/runtime/sql) (js-bun-db.mdc)`
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (bad === 0) {
|
|
108
|
+
pass(`js-bun-db: жоден package.json не містить ${FORBIDDEN_DEPENDENCIES.join(' / ')} у dependencies`)
|
|
109
|
+
}
|
|
110
|
+
return bad
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Сканує JS/TS-джерела на небезпечні патерни Bun SQL.
|
|
115
|
+
* @param {string[]} sourcePaths абсолютні шляхи джерел
|
|
116
|
+
* @param {string} repoRoot абсолютний шлях до кореня
|
|
117
|
+
* @param {{ pass: (m: string) => void, fail: (m: string) => void }} reporter колбеки pass і fail з перевірки
|
|
118
|
+
* @returns {Promise<{ hasBunSqlImport: boolean, perRequest: number, unsafeCall: number, dynamicList: number }>}
|
|
119
|
+
* `hasBunSqlImport` — чи знайдено хоч один `import { sql|SQL } from 'bun'` у джерелах;
|
|
120
|
+
* решта — кількість порушень кожного типу.
|
|
121
|
+
*/
|
|
122
|
+
async function scanSourcesForBunSqlPatterns(sourcePaths, repoRoot, reporter) {
|
|
123
|
+
const { fail } = reporter
|
|
124
|
+
let hasBunSqlImport = false
|
|
125
|
+
let perRequest = 0
|
|
126
|
+
let unsafeCall = 0
|
|
127
|
+
let dynamicList = 0
|
|
128
|
+
|
|
129
|
+
for (const absPath of sourcePaths) {
|
|
130
|
+
const rel = relative(repoRoot, absPath).split('\\').join('/')
|
|
131
|
+
const content = await readFile(absPath, 'utf8')
|
|
132
|
+
if (!hasBunSqlImport && textHasBunSqlImport(content)) {
|
|
133
|
+
hasBunSqlImport = true
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
for (const v of findBunSqlPerRequestConnectionInText(content, rel)) {
|
|
137
|
+
perRequest++
|
|
138
|
+
fail(
|
|
139
|
+
`js-bun-db: ${rel}:${v.line} — не створюй new SQL(...) всередині функцій; ` +
|
|
140
|
+
`тримай singleton на рівні модуля (js-bun-db.mdc): ${v.snippet}`
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
for (const v of findUnsafeBunSqlUnsafeCallInText(content, rel)) {
|
|
144
|
+
unsafeCall++
|
|
145
|
+
fail(
|
|
146
|
+
`js-bun-db: ${rel}:${v.line} — sql.unsafe(\`...\${...}...\`) недопустимо: ` +
|
|
147
|
+
`використовуй tagged template sql\`...\${value}...\` або sql.unsafe('static', [params]) (js-bun-db.mdc): ${v.snippet}`
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
for (const v of findUnsafeBunSqlDynamicSqlListInText(content, rel)) {
|
|
151
|
+
dynamicList++
|
|
152
|
+
fail(
|
|
153
|
+
`js-bun-db: ${rel}:${v.line} — заборонено підставляти у SQL динамічні списки через .join(',') ` +
|
|
154
|
+
`у IN (...) / VALUES (...); використовуй sql([...]) (js-bun-db.mdc): ${v.snippet}`
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return { hasBunSqlImport, perRequest, unsafeCall, dynamicList }
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Перевіряє відповідність проєкту правилу js-bun-db.mdc
|
|
164
|
+
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
165
|
+
*/
|
|
166
|
+
export async function check() {
|
|
167
|
+
const reporter = createCheckReporter()
|
|
168
|
+
const { pass } = reporter
|
|
169
|
+
|
|
170
|
+
const repoRoot = process.cwd()
|
|
171
|
+
const rootPkg = join(repoRoot, 'package.json')
|
|
172
|
+
if (!existsSync(rootPkg)) {
|
|
173
|
+
pass('js-bun-db: package.json у корені відсутній — перевірку пропущено')
|
|
174
|
+
return reporter.getExitCode()
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const pkgJsonPaths = await findAllPackageJsonPaths(repoRoot)
|
|
178
|
+
if (pkgJsonPaths.length === 0) {
|
|
179
|
+
pass('js-bun-db: package.json не знайдено — перевірку пропущено')
|
|
180
|
+
return reporter.getExitCode()
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
await checkForbiddenDependencies(pkgJsonPaths, repoRoot, reporter)
|
|
184
|
+
|
|
185
|
+
const sourcePaths = await findAllSourcePathsForBunSqlScan(repoRoot)
|
|
186
|
+
if (sourcePaths.length === 0) {
|
|
187
|
+
pass('js-bun-db: немає JS/TS файлів для скану патернів Bun SQL')
|
|
188
|
+
return reporter.getExitCode()
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const { hasBunSqlImport, perRequest, unsafeCall, dynamicList } = await scanSourcesForBunSqlPatterns(
|
|
192
|
+
sourcePaths,
|
|
193
|
+
repoRoot,
|
|
194
|
+
reporter
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
if (!hasBunSqlImport) {
|
|
198
|
+
pass("js-bun-db: Bun SQL не використовується в коді (немає import { sql|SQL } from 'bun')")
|
|
199
|
+
return reporter.getExitCode()
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (perRequest === 0) {
|
|
203
|
+
pass('js-bun-db: немає створення new SQL(...) всередині функцій (singleton на рівні модуля)')
|
|
204
|
+
}
|
|
205
|
+
if (unsafeCall === 0) {
|
|
206
|
+
pass('js-bun-db: немає небезпечних викликів sql.unsafe з інтерполяцією в шаблонному рядку')
|
|
207
|
+
}
|
|
208
|
+
if (dynamicList === 0) {
|
|
209
|
+
pass("js-bun-db: немає небезпечних динамічних SQL-списків через .join(',') у IN/VALUES")
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return reporter.getExitCode()
|
|
213
|
+
}
|
|
@@ -2,18 +2,24 @@
|
|
|
2
2
|
* Перевіряє лінт JavaScript за правилом js-lint.mdc.
|
|
3
3
|
*
|
|
4
4
|
* Канонічний `lint-js`, flat ESLint з getConfig і ignore для auto-imports, рекомендації VSCode,
|
|
5
|
-
* `.oxlintrc.json` з
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* `.oxlintrc.json` має збігатися з каноном oxlint у пакеті (`npm/scripts/utils/oxlint-canonical.json`):
|
|
6
|
+
* plugins, jsPlugins, categories, усі правила з канону (додаткові записи в `rules` дозволені), settings, env,
|
|
7
|
+
* globals, ignorePatterns. `@nitra/eslint-config` у devDependencies мінімум **3.6.12** (транзитивний
|
|
8
|
+
* `@e18e/eslint-plugin` для oxlint), `.jscpd.json` (gitignore, exitCode, reporters, minLines), workflow
|
|
9
|
+
* `lint-js.yml` (checkout@v6, setup-bun-deps, bunx без --fix), без prettier, `engines.node` >= 24,
|
|
10
|
+
* `"type": "module"` у кореневому і всіх workspace `package.json`. Дубль перевірки JS у `lint.yml` — заборонено.
|
|
10
11
|
*/
|
|
11
12
|
import { existsSync } from 'node:fs'
|
|
12
13
|
import { readFile } from 'node:fs/promises'
|
|
14
|
+
import { dirname, join } from 'node:path'
|
|
15
|
+
import { fileURLToPath } from 'node:url'
|
|
13
16
|
|
|
14
17
|
import { parseWorkflowYaml, verifyLintJsWorkflowStructure } from './utils/gha-workflow.mjs'
|
|
15
18
|
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
16
19
|
|
|
20
|
+
/** Шлях до канонічного oxlint JSON у цьому пакеті (для перевірки та тестів). */
|
|
21
|
+
export const OXLINT_CANONICAL_JSON_PATH = join(dirname(fileURLToPath(import.meta.url)), 'utils', 'oxlint-canonical.json')
|
|
22
|
+
|
|
17
23
|
/** Очікуваний локальний скрипт. */
|
|
18
24
|
export const CANONICAL_LINT_JS = 'bunx oxlint --fix && bunx eslint --fix . && bunx jscpd .'
|
|
19
25
|
|
|
@@ -43,9 +49,9 @@ export function isCanonicalLintJs(script) {
|
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
/**
|
|
46
|
-
* Чи діапазон `@nitra/eslint-config` у `package.json` передбачає версію з транзитивним `@e18e/eslint-plugin` (>=3.
|
|
52
|
+
* Чи діапазон `@nitra/eslint-config` у `package.json` передбачає версію з транзитивним `@e18e/eslint-plugin` (>=3.6.12).
|
|
47
53
|
* @param {unknown} versionSpec значення `devDependencies['@nitra/eslint-config']`
|
|
48
|
-
* @returns {boolean} true для `workspace:*` або першої semver у рядку >= 3.
|
|
54
|
+
* @returns {boolean} true для `workspace:*` або першої semver у рядку >= 3.6.12
|
|
49
55
|
*/
|
|
50
56
|
export function nitraEslintConfigDeclaresE18eTransitive(versionSpec) {
|
|
51
57
|
const s = String(versionSpec).trim()
|
|
@@ -64,29 +70,97 @@ export function nitraEslintConfigDeclaresE18eTransitive(versionSpec) {
|
|
|
64
70
|
}
|
|
65
71
|
|
|
66
72
|
/**
|
|
67
|
-
*
|
|
68
|
-
* @param {unknown}
|
|
69
|
-
* @
|
|
73
|
+
* Рекурсивне порівняння фрагментів канону oxlint (масиви — порядок як у каноні; об’єкти — той самий набір ключів і вкладеність).
|
|
74
|
+
* @param {unknown} actual значення з `.oxlintrc.json`
|
|
75
|
+
* @param {unknown} expected значення з канону
|
|
76
|
+
* @returns {boolean} true, якщо значення збігаються за правилами канону
|
|
77
|
+
*/
|
|
78
|
+
function deepEqualOxlintCanonical(actual, expected) {
|
|
79
|
+
if (expected === null || typeof expected !== 'object') {
|
|
80
|
+
return actual === expected
|
|
81
|
+
}
|
|
82
|
+
if (Array.isArray(expected)) {
|
|
83
|
+
return Array.isArray(actual) && JSON.stringify(actual) === JSON.stringify(expected)
|
|
84
|
+
}
|
|
85
|
+
if (typeof actual !== 'object' || actual === null || Array.isArray(actual)) {
|
|
86
|
+
return false
|
|
87
|
+
}
|
|
88
|
+
const exp = /** @type {Record<string, unknown>} */ (expected)
|
|
89
|
+
const act = /** @type {Record<string, unknown>} */ (actual)
|
|
90
|
+
const expKeys = Object.keys(exp)
|
|
91
|
+
const actKeys = Object.keys(act)
|
|
92
|
+
if (expKeys.length !== actKeys.length) {
|
|
93
|
+
return false
|
|
94
|
+
}
|
|
95
|
+
for (const k of expKeys) {
|
|
96
|
+
if (!(k in act) || !deepEqualOxlintCanonical(act[k], exp[k])) {
|
|
97
|
+
return false
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return true
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Безпечний доступ як до plain-object запису.
|
|
105
|
+
* @param {unknown} v будь-яке значення
|
|
106
|
+
* @returns {Record<string, unknown>} запис або пустий обʼєкт, якщо `v` не plain-object
|
|
107
|
+
*/
|
|
108
|
+
function asRecordOrEmpty(v) {
|
|
109
|
+
return v && typeof v === 'object' && !Array.isArray(v) ? /** @type {Record<string, unknown>} */ (v) : {}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Звіряє блок `rules`: кожне правило з канону має точне збіжне значення в actual.
|
|
114
|
+
* @param {unknown} expected канонічне значення для `rules`
|
|
115
|
+
* @param {unknown} actual поточне значення для `rules`
|
|
116
|
+
* @param {string[]} failures буфер для помилок
|
|
117
|
+
*/
|
|
118
|
+
function compareOxlintRules(expected, actual, failures) {
|
|
119
|
+
const er = asRecordOrEmpty(expected)
|
|
120
|
+
const ar = asRecordOrEmpty(actual)
|
|
121
|
+
for (const ruleKey of Object.keys(er)) {
|
|
122
|
+
if (ar[ruleKey] !== er[ruleKey]) {
|
|
123
|
+
failures.push(
|
|
124
|
+
`.oxlintrc.json: rules["${ruleKey}"] очікується ${JSON.stringify(er[ruleKey])}, зараз ${JSON.stringify(ar[ruleKey])}`
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Перевіряє `.oxlintrc.json` проти канону пакета `@nitra/cursor` (усі правила з канону та інші поля з `oxlint-canonical.json`).
|
|
132
|
+
* Додаткові ключі лише в `rules` дозволені; інші поля мають збігатися з каноном.
|
|
133
|
+
* @param {unknown} cfg корінь JSON з `.oxlintrc.json`
|
|
134
|
+
* @param {unknown} canonical розпарений `oxlint-canonical.json`
|
|
135
|
+
* @returns {{ ok: boolean, failures: string[] }} статус і повідомлення для `fail`
|
|
70
136
|
*/
|
|
71
|
-
export function
|
|
137
|
+
export function verifyOxlintRcAgainstCanonical(cfg, canonical) {
|
|
72
138
|
const failures = []
|
|
73
139
|
if (!cfg || typeof cfg !== 'object' || Array.isArray(cfg)) {
|
|
74
140
|
return { ok: false, failures: ['.oxlintrc.json: корінь має бути значенням типу object'] }
|
|
75
141
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (!Array.isArray(jsPlugins) || !jsPlugins.includes('@e18e/eslint-plugin')) {
|
|
79
|
-
failures.push('.oxlintrc.json: jsPlugins має містити "@e18e/eslint-plugin"')
|
|
142
|
+
if (!canonical || typeof canonical !== 'object' || Array.isArray(canonical)) {
|
|
143
|
+
return { ok: false, failures: ['внутрішня помилка: канон oxlint має бути object'] }
|
|
80
144
|
}
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
145
|
+
const o = /** @type {Record<string, unknown>} */ (cfg)
|
|
146
|
+
const c = /** @type {Record<string, unknown>} */ (canonical)
|
|
147
|
+
|
|
148
|
+
for (const key of Object.keys(c)) {
|
|
149
|
+
const expected = c[key]
|
|
150
|
+
const actual = o[key]
|
|
151
|
+
|
|
152
|
+
if (key === 'rules') {
|
|
153
|
+
compareOxlintRules(expected, actual, failures)
|
|
154
|
+
continue
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (!deepEqualOxlintCanonical(actual, expected)) {
|
|
158
|
+
failures.push(
|
|
159
|
+
`.oxlintrc.json: поле "${key}" має збігатися з каноном пакета @nitra/cursor (npm/scripts/utils/oxlint-canonical.json)`
|
|
160
|
+
)
|
|
88
161
|
}
|
|
89
162
|
}
|
|
163
|
+
|
|
90
164
|
return { ok: failures.length === 0, failures }
|
|
91
165
|
}
|
|
92
166
|
|
|
@@ -96,7 +170,7 @@ export function verifyOxlintRcE18e(cfg) {
|
|
|
96
170
|
* @param {(msg: string) => void} failFn callback при помилці
|
|
97
171
|
*/
|
|
98
172
|
async function checkEslintConfig(passFn, failFn) {
|
|
99
|
-
let eslintPath
|
|
173
|
+
let eslintPath
|
|
100
174
|
if (existsSync('eslint.config.js')) {
|
|
101
175
|
eslintPath = 'eslint.config.js'
|
|
102
176
|
passFn('eslint.config.js існує')
|
|
@@ -157,10 +231,12 @@ function checkPackageJsonLintDeps(pkg, passFn, failFn) {
|
|
|
157
231
|
if (nitraEslint) {
|
|
158
232
|
passFn('@nitra/eslint-config є в devDependencies')
|
|
159
233
|
if (nitraEslintConfigDeclaresE18eTransitive(nitraEslint)) {
|
|
160
|
-
passFn(
|
|
234
|
+
passFn(
|
|
235
|
+
'@nitra/eslint-config: мінімум 3.6.12 (транзитивний @e18e/eslint-plugin для oxlint jsPlugins, js-lint.mdc)'
|
|
236
|
+
)
|
|
161
237
|
} else {
|
|
162
238
|
failFn(
|
|
163
|
-
'@nitra/eslint-config: онови до мінімум "^3.
|
|
239
|
+
'@nitra/eslint-config: онови до мінімум "^3.6.12" — з цієї версії постачається @e18e/eslint-plugin для .oxlintrc.json (js-lint.mdc)'
|
|
164
240
|
)
|
|
165
241
|
}
|
|
166
242
|
} else {
|
|
@@ -269,9 +345,16 @@ async function checkOxlintRc(passFn, failFn) {
|
|
|
269
345
|
return
|
|
270
346
|
}
|
|
271
347
|
passFn('.oxlintrc.json існує')
|
|
272
|
-
|
|
348
|
+
let canonical
|
|
349
|
+
try {
|
|
350
|
+
canonical = JSON.parse(await readFile(OXLINT_CANONICAL_JSON_PATH, 'utf8'))
|
|
351
|
+
} catch {
|
|
352
|
+
failFn('внутрішня помилка: не вдалося прочитати канон oxlint з пакета @nitra/cursor')
|
|
353
|
+
return
|
|
354
|
+
}
|
|
355
|
+
const oxV = verifyOxlintRcAgainstCanonical(oxCfg, canonical)
|
|
273
356
|
if (oxV.ok) {
|
|
274
|
-
passFn('.oxlintrc.json
|
|
357
|
+
passFn('.oxlintrc.json збігається з каноном oxlint (@nitra/cursor)')
|
|
275
358
|
} else {
|
|
276
359
|
for (const msg of oxV.failures) {
|
|
277
360
|
failFn(msg)
|