@nitra/cursor 1.8.156 → 1.8.157

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.
@@ -124,11 +124,8 @@ async function checkForbiddenDependencies(pkgJsonPaths, repoRoot, reporter) {
124
124
  */
125
125
  async function scanSourcesForBunSqlPatterns(sourcePaths, repoRoot, reporter) {
126
126
  const { fail } = reporter
127
+ const counts = { perRequest: 0, unsafeCall: 0, dynamicList: 0, inListGuard: 0 }
127
128
  let hasBunSqlImport = false
128
- let perRequest = 0
129
- let unsafeCall = 0
130
- let dynamicList = 0
131
- let inListGuard = 0
132
129
 
133
130
  for (const absPath of sourcePaths) {
134
131
  const rel = relative(repoRoot, absPath).split('\\').join('/')
@@ -136,50 +133,72 @@ async function scanSourcesForBunSqlPatterns(sourcePaths, repoRoot, reporter) {
136
133
  if (!hasBunSqlImport && textHasBunSqlImport(content)) {
137
134
  hasBunSqlImport = true
138
135
  }
136
+ scanFileForBunSqlPatterns(content, rel, fail, counts)
137
+ }
139
138
 
140
- for (const v of findBunSqlPerRequestConnectionInText(content, rel)) {
141
- perRequest++
142
- fail(
143
- `js-bun-db: ${rel}:${v.line} — не створюй new SQL(...) всередині функцій; ` +
144
- `тримай singleton на рівні модуля (js-bun-db.mdc): ${v.snippet}`
145
- )
146
- }
147
- for (const v of findUnsafeBunSqlUnsafeCallInText(content, rel)) {
148
- unsafeCall++
149
- fail(
150
- `js-bun-db: ${rel}:${v.line} — sql.unsafe(\`...\${...}...\`) недопустимо: ` +
151
- `використовуй tagged template sql\`...\${value}...\` або sql.unsafe('static', [params]) (js-bun-db.mdc): ${v.snippet}`
152
- )
153
- }
154
- for (const v of findUnsafeBunSqlDynamicSqlListInText(content, rel)) {
155
- dynamicList++
156
- fail(
157
- `js-bun-db: ${rel}:${v.line} — заборонено підставляти у SQL динамічні списки через .join(',') ` +
158
- `у IN (...) / VALUES (...); використовуй sql([...]) (js-bun-db.mdc): ${v.snippet}`
159
- )
160
- }
161
- for (const v of findUnsafeBunSqlInListMissingEmptyGuardInText(content, rel)) {
162
- inListGuard++
163
- if (v.reason === 'missing_guard') {
164
- fail(
165
- `js-bun-db: ${rel}:${v.line} — перед IN-списком ${JSON.stringify(v.name)} потрібна перевірка на пустоту ` +
166
- throw (наприклад if (!${v.name}.length) throw ...), інакше можливі некоректні запити (js-bun-db.mdc): ${v.snippet}`
167
- )
168
- } else if (v.reason === 'sql_helper_not_var') {
169
- fail(
170
- `js-bun-db: ${rel}:${v.line} — IN-список у \${sql(...)} має підставлятись зі змінної (Identifier) ` +
171
- `після валідації на пустоту + throw (js-bun-db.mdc): ${v.snippet}`
172
- )
173
- } else {
174
- fail(
175
- `js-bun-db: ${rel}:${v.line} — значення для IN (...) у template literal треба винести в окрему змінну ` +
176
- `і перевірити на пустоту (throw), не підставляти вираз напряму (js-bun-db.mdc): ${v.snippet}`
177
- )
178
- }
179
- }
139
+ return { hasBunSqlImport, ...counts }
140
+ }
141
+
142
+ /**
143
+ * Сканує один файл усіма AST-сканерами bun-sql і реєструє знайдені порушення.
144
+ * @param {string} content вміст файлу
145
+ * @param {string} rel posix-шлях відносно `repoRoot`
146
+ * @param {(msg: string) => void} fail callback при помилці
147
+ * @param {{ perRequest: number, unsafeCall: number, dynamicList: number, inListGuard: number }} counts акумулятори
148
+ * @returns {void}
149
+ */
150
+ function scanFileForBunSqlPatterns(content, rel, fail, counts) {
151
+ for (const v of findBunSqlPerRequestConnectionInText(content, rel)) {
152
+ counts.perRequest++
153
+ fail(
154
+ `js-bun-db: ${rel}:${v.line} — не створюй new SQL(...) всередині функцій; ` +
155
+ `тримай singleton на рівні модуля (js-bun-db.mdc): ${v.snippet}`
156
+ )
157
+ }
158
+ for (const v of findUnsafeBunSqlUnsafeCallInText(content, rel)) {
159
+ counts.unsafeCall++
160
+ fail(
161
+ `js-bun-db: ${rel}:${v.line} — sql.unsafe(\`...\${...}...\`) недопустимо: ` +
162
+ `використовуй tagged template sql\`...\${value}...\` або sql.unsafe('static', [params]) (js-bun-db.mdc): ${v.snippet}`
163
+ )
164
+ }
165
+ for (const v of findUnsafeBunSqlDynamicSqlListInText(content, rel)) {
166
+ counts.dynamicList++
167
+ fail(
168
+ `js-bun-db: ${rel}:${v.line} — заборонено підставляти у SQL динамічні списки через .join(',') ` +
169
+ IN (...) / VALUES (...); використовуй sql([...]) (js-bun-db.mdc): ${v.snippet}`
170
+ )
171
+ }
172
+ for (const v of findUnsafeBunSqlInListMissingEmptyGuardInText(content, rel)) {
173
+ counts.inListGuard++
174
+ fail(messageForBunSqlInListGuard(rel, v))
180
175
  }
176
+ }
181
177
 
182
- return { hasBunSqlImport, perRequest, unsafeCall, dynamicList, inListGuard }
178
+ /**
179
+ * Будує повідомлення `fail` для порушення `findUnsafeBunSqlInListMissingEmptyGuardInText`
180
+ * залежно від `reason` (різні діагностики однакового сімейства).
181
+ * @param {string} rel posix-шлях відносно кореня репо
182
+ * @param {{ line: number, snippet: string, name?: string, reason: string }} v порушення
183
+ * @returns {string} готове повідомлення для `fail`
184
+ */
185
+ function messageForBunSqlInListGuard(rel, v) {
186
+ if (v.reason === 'missing_guard') {
187
+ return (
188
+ `js-bun-db: ${rel}:${v.line} — перед IN-списком ${JSON.stringify(v.name)} потрібна перевірка на пустоту ` +
189
+ `з throw (наприклад if (!${v.name}.length) throw ...), інакше можливі некоректні запити (js-bun-db.mdc): ${v.snippet}`
190
+ )
191
+ }
192
+ if (v.reason === 'sql_helper_not_var') {
193
+ return (
194
+ `js-bun-db: ${rel}:${v.line} — IN-список у \${sql(...)} має підставлятись зі змінної (Identifier) ` +
195
+ `після валідації на пустоту + throw (js-bun-db.mdc): ${v.snippet}`
196
+ )
197
+ }
198
+ return (
199
+ `js-bun-db: ${rel}:${v.line} — значення для IN (...) у template literal треба винести в окрему змінну ` +
200
+ `і перевірити на пустоту (throw), не підставляти вираз напряму (js-bun-db.mdc): ${v.snippet}`
201
+ )
183
202
  }
184
203
 
185
204
  /**
@@ -4,7 +4,8 @@
4
4
  * Канонічний `lint-js`, flat ESLint з getConfig і ignore для auto-imports, рекомендації VSCode,
5
5
  * `.oxlintrc.json` має збігатися з каноном oxlint у пакеті (`npm/scripts/utils/oxlint-canonical.json`):
6
6
  * plugins, jsPlugins, categories, усі правила з канону (додаткові записи в `rules` дозволені), settings, env,
7
- * globals, ignorePatterns. `@nitra/eslint-config` у devDependencies мінімум **3.6.12** (транзитивний
7
+ * globals, ignorePatterns. `@nitra/eslint-config` у devDependencies мінімум **3.8.0** (з цієї версії
8
+ * правило `no-restricted-syntax` для `ForInStatement` забороняє `for...in`; також тягне транзитивний
8
9
  * `@e18e/eslint-plugin` для oxlint), `.jscpd.json` (gitignore, exitCode, reporters, minLines), workflow
9
10
  * `lint-js.yml` (checkout@v6, setup-bun-deps, bunx без --fix), без prettier, `engines.node` >= 24,
10
11
  * `engines.bun` >= 1.3, `"type": "module"` у кореневому і всіх workspace `package.json`. Дубль перевірки JS у `lint.yml` — заборонено.
@@ -53,11 +54,12 @@ export function isCanonicalLintJs(script) {
53
54
  }
54
55
 
55
56
  /**
56
- * Чи діапазон `@nitra/eslint-config` у `package.json` передбачає версію з транзитивним `@e18e/eslint-plugin` (>=3.6.12).
57
+ * Чи діапазон `@nitra/eslint-config` у `package.json` задовольняє мінімум `>= 3.8.0`
58
+ * (заборона `for...in` через `no-restricted-syntax` + транзитивний `@e18e/eslint-plugin` для oxlint).
57
59
  * @param {unknown} versionSpec значення `devDependencies['@nitra/eslint-config']`
58
- * @returns {boolean} true для `workspace:*` або першої semver у рядку >= 3.6.12
60
+ * @returns {boolean} true для `workspace:*` або першої semver у рядку >= 3.8.0
59
61
  */
60
- export function nitraEslintConfigDeclaresE18eTransitive(versionSpec) {
62
+ export function nitraEslintConfigMeetsMinVersion(versionSpec) {
61
63
  const s = String(versionSpec).trim()
62
64
  if (s.startsWith('workspace:')) {
63
65
  return true
@@ -70,7 +72,7 @@ export function nitraEslintConfigDeclaresE18eTransitive(versionSpec) {
70
72
  if ([major, minor, patch].some(n => Number.isNaN(n))) {
71
73
  return false
72
74
  }
73
- return major > 3 || (major === 3 && minor > 5) || (major === 3 && minor === 5 && patch >= 0)
75
+ return major > 3 || (major === 3 && minor >= 8 && patch >= 0)
74
76
  }
75
77
 
76
78
  /**
@@ -234,13 +236,13 @@ function checkPackageJsonLintDeps(pkg, passFn, failFn) {
234
236
  const nitraEslint = pkg.devDependencies?.['@nitra/eslint-config']
235
237
  if (nitraEslint) {
236
238
  passFn('@nitra/eslint-config є в devDependencies')
237
- if (nitraEslintConfigDeclaresE18eTransitive(nitraEslint)) {
239
+ if (nitraEslintConfigMeetsMinVersion(nitraEslint)) {
238
240
  passFn(
239
- '@nitra/eslint-config: мінімум 3.6.12 (транзитивний @e18e/eslint-plugin для oxlint jsPlugins, js-lint.mdc)'
241
+ '@nitra/eslint-config: мінімум 3.8.0 (no-restricted-syntax для ForInStatement + @e18e/eslint-plugin транзитивно, js-lint.mdc)'
240
242
  )
241
243
  } else {
242
244
  failFn(
243
- '@nitra/eslint-config: онови до мінімум "^3.6.12" — з цієї версії постачається @e18e/eslint-plugin для .oxlintrc.json (js-lint.mdc)'
245
+ '@nitra/eslint-config: онови до мінімум "^3.8.0" — з цієї версії правило no-restricted-syntax забороняє for...in (плюс транзитивний @e18e/eslint-plugin для oxlint, js-lint.mdc)'
244
246
  )
245
247
  }
246
248
  } else {
@@ -16,7 +16,7 @@
16
16
  * з `@nitra/check-env` (для обов'язкових змінних, із `checkEnv([...])`) або з
17
17
  * `node:process` (для опційних). Коли `env` імпортовано з `@nitra/check-env`,
18
18
  * кожен `env.X` має бути закритий літеральним викликом `checkEnv(['X', ...])`
19
- * у тому ж файлі або коментарем `// @nitra/cursor ignore-next-line checkEnv`
19
+ * у тому ж файлі або коментарем `// \@nitra/cursor ignore-next-line checkEnv`
20
20
  * на попередньому рядку (див. `utils/check-env-scan.mjs`).
21
21
  */
22
22
  import { existsSync } from 'node:fs'
@@ -156,22 +156,7 @@ async function checkProcessEnvUsage(absPackageRoot, sourcePaths, label, fail) {
156
156
  async function checkWorkspacePackage(rootDir, fail, passFn) {
157
157
  const label = `[${rootDir}] `
158
158
  const absPackageRoot = join(process.cwd(), rootDir)
159
- /** @type {unknown} */
160
- let pkgJson = null
161
- const pkgPath = join(rootDir, 'package.json')
162
- if (existsSync(pkgPath)) {
163
- pkgJson = JSON.parse(await readFile(pkgPath, 'utf8'))
164
- const deps = /** @type {Record<string, unknown>} */ (pkgJson).dependencies
165
- const devDeps = /** @type {Record<string, unknown>} */ (pkgJson).devDependencies
166
- const allDeps = { ...(deps || {}), ...(devDeps || {}) }
167
-
168
- if (allDeps['@nitra/bunyan']) {
169
- fail(`${label}@nitra/bunyan знайдено — замінити на @nitra/pino`)
170
- }
171
- if (allDeps.bunyan) {
172
- fail(`${label}bunyan знайдено — замінити на @nitra/pino`)
173
- }
174
- }
159
+ const pkgJson = await loadPackageJsonAndCheckBunyanDeps(rootDir, label, fail)
175
160
 
176
161
  const importViolations = await checkBunyanImports(absPackageRoot, label, fail)
177
162
  if (importViolations === 0) {
@@ -193,19 +178,54 @@ async function checkWorkspacePackage(rootDir, fail, passFn) {
193
178
  )
194
179
  }
195
180
 
181
+ await checkOtelConfigmap(rootDir, label, fail, passFn)
182
+ }
183
+
184
+ /**
185
+ * Завантажує `package.json` пакета (якщо є) і реєструє порушення для bunyan-залежностей.
186
+ * @param {string} rootDir відносний шлях workspace
187
+ * @param {string} label префікс повідомлення `[<pkg>] `
188
+ * @param {(msg: string) => void} fail callback при помилці
189
+ * @returns {Promise<unknown>} розпарсений package.json або null
190
+ */
191
+ async function loadPackageJsonAndCheckBunyanDeps(rootDir, label, fail) {
192
+ const pkgPath = join(rootDir, 'package.json')
193
+ if (!existsSync(pkgPath)) return null
194
+ const pkgJson = JSON.parse(await readFile(pkgPath, 'utf8'))
195
+ const deps = /** @type {Record<string, unknown>} */ (pkgJson).dependencies
196
+ const devDeps = /** @type {Record<string, unknown>} */ (pkgJson).devDependencies
197
+ const allDeps = { ...deps, ...devDeps }
198
+ if (allDeps['@nitra/bunyan']) {
199
+ fail(`${label}@nitra/bunyan знайдено — замінити на @nitra/pino`)
200
+ }
201
+ if (allDeps.bunyan) {
202
+ fail(`${label}bunyan знайдено — замінити на @nitra/pino`)
203
+ }
204
+ return pkgJson
205
+ }
206
+
207
+ /**
208
+ * Перевіряє вміст `k8s/base/configmap.yaml` пакета на наявність OTEL_RESOURCE_ATTRIBUTES
209
+ * з обов'язковими `service.name=` та `service.namespace=` всередині.
210
+ * @param {string} rootDir відносний шлях workspace
211
+ * @param {string} label префікс повідомлення `[<pkg>] `
212
+ * @param {(msg: string) => void} fail callback при помилці
213
+ * @param {(msg: string) => void} passFn успішне повідомлення
214
+ * @returns {Promise<void>} завершується після перевірки configmap
215
+ */
216
+ async function checkOtelConfigmap(rootDir, label, fail, passFn) {
196
217
  const configmapPath = join(rootDir, 'k8s', 'base', 'configmap.yaml')
197
- if (existsSync(configmapPath)) {
198
- const content = await readFile(configmapPath, 'utf8')
199
- if (content.includes('OTEL_RESOURCE_ATTRIBUTES')) {
200
- passFn(`${label}k8s/base/configmap.yaml містить OTEL_RESOURCE_ATTRIBUTES`)
201
- if (content.includes('service.name=') && content.includes('service.namespace=')) {
202
- passFn(`${label}OTEL_RESOURCE_ATTRIBUTES містить service.name та service.namespace`)
203
- } else {
204
- fail(`${label}OTEL_RESOURCE_ATTRIBUTES має містити service.name=<name>,service.namespace=<namespace>`)
205
- }
206
- } else {
207
- fail(`${label}k8s/base/configmap.yaml не містить OTEL_RESOURCE_ATTRIBUTES`)
208
- }
218
+ if (!existsSync(configmapPath)) return
219
+ const content = await readFile(configmapPath, 'utf8')
220
+ if (!content.includes('OTEL_RESOURCE_ATTRIBUTES')) {
221
+ fail(`${label}k8s/base/configmap.yaml не містить OTEL_RESOURCE_ATTRIBUTES`)
222
+ return
223
+ }
224
+ passFn(`${label}k8s/base/configmap.yaml містить OTEL_RESOURCE_ATTRIBUTES`)
225
+ if (content.includes('service.name=') && content.includes('service.namespace=')) {
226
+ passFn(`${label}OTEL_RESOURCE_ATTRIBUTES містить service.name та service.namespace`)
227
+ } else {
228
+ fail(`${label}OTEL_RESOURCE_ATTRIBUTES має містити service.name=<name>,service.namespace=<namespace>`)
209
229
  }
210
230
  }
211
231