@nitra/cursor 12.16.2 → 12.17.0
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 +6 -0
- package/package.json +1 -1
- package/scripts/auto-rules.mjs +34 -58
- package/scripts/lib/rule-predicates.mjs +15 -60
- package/scripts/utils/walkDir.mjs +1 -1
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
package/scripts/auto-rules.mjs
CHANGED
|
@@ -16,10 +16,13 @@
|
|
|
16
16
|
* їх у конфіг із поправкою на legacy-id (`migrateRuleIds`).
|
|
17
17
|
*/
|
|
18
18
|
import { readdirSync } from 'node:fs'
|
|
19
|
-
import {
|
|
19
|
+
import { readFile } from 'node:fs/promises'
|
|
20
20
|
import { basename, dirname, join, relative } from 'node:path'
|
|
21
21
|
import { fileURLToPath } from 'node:url'
|
|
22
22
|
|
|
23
|
+
import { globby } from 'globby'
|
|
24
|
+
|
|
25
|
+
import { ALWAYS_IGNORE } from './utils/walkDir.mjs'
|
|
23
26
|
import { globToRegex } from '../rules/npm-module/js/package_structure.mjs'
|
|
24
27
|
import { textHasBunSqlImport } from '../rules/js-bun-db/lib/bun-sql-scan.mjs'
|
|
25
28
|
import {
|
|
@@ -84,9 +87,30 @@ export const AUTO_RULE_DEPENDENCIES = Object.freeze(
|
|
|
84
87
|
|
|
85
88
|
const HASURA_CONFIG_MARKER = 'metadata_directory: metadata'
|
|
86
89
|
const REGO_RE = /\.rego$/iu
|
|
87
|
-
const IGNORED_DIR_NAMES = new Set(['node_modules', '.git', '.next', '.turbo'])
|
|
88
90
|
const DEFAULT_DISABLED_LIST = Object.freeze([])
|
|
89
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Збирає relative-posix шляхи дерева (файли + директорії), **поважаючи `.gitignore`** —
|
|
94
|
+
* через той самий `globby`-канон, що й `walkDir` (звідси `ALWAYS_IGNORE`). Спільне джерело
|
|
95
|
+
* для `collectRepoPaths` (Type A glob-матчинг) і `collectAutoRuleFacts` (content-факти).
|
|
96
|
+
* Раніше тут був ручний `readdir`-обхід із хардкод skip-набором, який ігнорував `.gitignore`
|
|
97
|
+
* і помилково активував правила на згенерованих артефактах (`coverage/*.png` → image-compress).
|
|
98
|
+
* @param {string} root абсолютний шлях кореня репозиторію
|
|
99
|
+
* @returns {Promise<{ files: string[], dirs: string[] }>} relative-posix шляхи файлів і директорій
|
|
100
|
+
*/
|
|
101
|
+
async function collectTreePaths(root) {
|
|
102
|
+
const opts = { cwd: root, gitignore: true, dot: true, ignore: ALWAYS_IGNORE }
|
|
103
|
+
try {
|
|
104
|
+
const [files, dirs] = await Promise.all([
|
|
105
|
+
globby('**/*', { ...opts, onlyFiles: true }),
|
|
106
|
+
globby('**/*', { ...opts, onlyDirectories: true })
|
|
107
|
+
])
|
|
108
|
+
return { files, dirs }
|
|
109
|
+
} catch {
|
|
110
|
+
return { files: [], dirs: [] }
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
90
114
|
/**
|
|
91
115
|
* Чи містить текст джерела імпорт імені `sql` або `SQL` з `"bun"` (після витягування `<script>` у `.vue`).
|
|
92
116
|
* @param {string} content вміст файлу
|
|
@@ -224,35 +248,13 @@ export async function collectAutoRuleFacts(root) {
|
|
|
224
248
|
hasTempoDir: false
|
|
225
249
|
}
|
|
226
250
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
let entries
|
|
234
|
-
try {
|
|
235
|
-
entries = await readdir(dir, { withFileTypes: true })
|
|
236
|
-
} catch {
|
|
237
|
-
return
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
for (const entry of entries) {
|
|
241
|
-
const absPath = join(dir, entry.name)
|
|
242
|
-
if (entry.isDirectory()) {
|
|
243
|
-
if (!IGNORED_DIR_NAMES.has(entry.name)) {
|
|
244
|
-
if (entry.name === 'tempo') {
|
|
245
|
-
facts.hasTempoDir = true
|
|
246
|
-
}
|
|
247
|
-
await walk(absPath)
|
|
248
|
-
}
|
|
249
|
-
} else if (entry.isFile()) {
|
|
250
|
-
await processFileEntry(absPath, root, facts)
|
|
251
|
-
}
|
|
252
|
-
}
|
|
251
|
+
const { files, dirs } = await collectTreePaths(root)
|
|
252
|
+
if (dirs.some(d => basename(d) === 'tempo')) {
|
|
253
|
+
facts.hasTempoDir = true
|
|
254
|
+
}
|
|
255
|
+
for (const rel of files) {
|
|
256
|
+
await processFileEntry(join(root, rel), root, facts)
|
|
253
257
|
}
|
|
254
|
-
|
|
255
|
-
await walk(root)
|
|
256
258
|
return facts
|
|
257
259
|
}
|
|
258
260
|
|
|
@@ -266,34 +268,8 @@ export async function collectAutoRuleFacts(root) {
|
|
|
266
268
|
* @returns {Promise<string[]>} шляхи відносно root у posix-форматі
|
|
267
269
|
*/
|
|
268
270
|
async function collectRepoPaths(root) {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* Рекурсивний обхід каталогу з пропуском службових директорій.
|
|
273
|
-
* @param {string} dir каталог
|
|
274
|
-
* @returns {Promise<void>}
|
|
275
|
-
*/
|
|
276
|
-
async function walk(dir) {
|
|
277
|
-
let entries
|
|
278
|
-
try {
|
|
279
|
-
entries = await readdir(dir, { withFileTypes: true })
|
|
280
|
-
} catch {
|
|
281
|
-
return
|
|
282
|
-
}
|
|
283
|
-
for (const entry of entries) {
|
|
284
|
-
const abs = join(dir, entry.name)
|
|
285
|
-
if (entry.isDirectory()) {
|
|
286
|
-
if (!IGNORED_DIR_NAMES.has(entry.name)) {
|
|
287
|
-
out.push(relative(root, abs).split('\\').join('/'))
|
|
288
|
-
await walk(abs)
|
|
289
|
-
}
|
|
290
|
-
} else if (entry.isFile()) {
|
|
291
|
-
out.push(relative(root, abs).split('\\').join('/'))
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
await walk(root)
|
|
296
|
-
return out
|
|
271
|
+
const { files, dirs } = await collectTreePaths(root)
|
|
272
|
+
return [...dirs, ...files]
|
|
297
273
|
}
|
|
298
274
|
|
|
299
275
|
/**
|
|
@@ -8,27 +8,23 @@
|
|
|
8
8
|
* Сигнатури неоднорідні (одні беруть `facts`, інші — `cwd`/`packageJson`), бо предикати
|
|
9
9
|
* читають різні джерела; виклик диспетчиться в `auto-rules.mjs` за іменем предиката.
|
|
10
10
|
*/
|
|
11
|
-
import {
|
|
11
|
+
import { readFile } from 'node:fs/promises'
|
|
12
12
|
import { join } from 'node:path'
|
|
13
13
|
|
|
14
|
+
import { findAllPackageJsonPaths } from '../utils/find-package-json-paths.mjs'
|
|
14
15
|
import { getRepositoryUrl } from './rule-meta-helpers.mjs'
|
|
15
16
|
|
|
16
|
-
const IGNORED_DIR_NAMES = new Set(['node_modules', '.git', '.next', '.turbo'])
|
|
17
|
-
|
|
18
17
|
/**
|
|
19
18
|
* Чи package.json дерева містить будь-який із зазначених пакетів у dependencies.
|
|
19
|
+
* Обхід — через `findAllPackageJsonPaths` (на `walkDir`/`globby`), тож **поважає `.gitignore`**
|
|
20
|
+
* і не зчитує package.json з ігнорованих каталогів (build-артефакти, vendored-копії).
|
|
20
21
|
* @param {string} root корінь репо
|
|
21
22
|
* @param {string[]} keys імена пакетів
|
|
22
23
|
* @returns {Promise<boolean>} true, якщо знайдено хоч один
|
|
23
24
|
*/
|
|
24
|
-
function anyDepInTree(root, keys) {
|
|
25
|
+
async function anyDepInTree(root, keys) {
|
|
25
26
|
const wanted = new Set(keys)
|
|
26
|
-
|
|
27
|
-
* Чи package.json за `abs` оголошує будь-який пакет із `wanted` у `dependencies`.
|
|
28
|
-
* @param {string} abs шлях до package.json
|
|
29
|
-
* @returns {Promise<boolean>} true, якщо знайдено хоч один
|
|
30
|
-
*/
|
|
31
|
-
async function pkgDeclaresWanted(abs) {
|
|
27
|
+
for (const abs of await findAllPackageJsonPaths(root, [])) {
|
|
32
28
|
try {
|
|
33
29
|
const deps = JSON.parse(await readFile(abs, 'utf8'))?.dependencies
|
|
34
30
|
if (deps && typeof deps === 'object' && !Array.isArray(deps)) {
|
|
@@ -37,70 +33,29 @@ function anyDepInTree(root, keys) {
|
|
|
37
33
|
} catch {
|
|
38
34
|
/* ігноруємо пошкоджені package.json */
|
|
39
35
|
}
|
|
40
|
-
return false
|
|
41
36
|
}
|
|
42
|
-
|
|
43
|
-
* @param {string} dir каталог обходу
|
|
44
|
-
* @returns {Promise<boolean>} true, якщо знайдено хоч один пакет
|
|
45
|
-
*/
|
|
46
|
-
async function walk(dir) {
|
|
47
|
-
let entries
|
|
48
|
-
try {
|
|
49
|
-
entries = await readdir(dir, { withFileTypes: true })
|
|
50
|
-
} catch {
|
|
51
|
-
return false
|
|
52
|
-
}
|
|
53
|
-
for (const entry of entries) {
|
|
54
|
-
const abs = join(dir, entry.name)
|
|
55
|
-
if (entry.isDirectory()) {
|
|
56
|
-
if (!IGNORED_DIR_NAMES.has(entry.name) && (await walk(abs))) return true
|
|
57
|
-
} else if (entry.isFile() && entry.name === 'package.json' && (await pkgDeclaresWanted(abs))) {
|
|
58
|
-
return true
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return false
|
|
62
|
-
}
|
|
63
|
-
return walk(root)
|
|
37
|
+
return false
|
|
64
38
|
}
|
|
65
39
|
|
|
66
40
|
/**
|
|
67
41
|
* Чи існує вкладений (не кореневий) package.json без `vite` у devDependencies.
|
|
42
|
+
* Обхід — `findAllPackageJsonPaths` (gitignore-aware), як у `anyDepInTree`.
|
|
68
43
|
* @param {string} root корінь репо
|
|
69
44
|
* @returns {Promise<boolean>} true, якщо знайдено
|
|
70
45
|
*/
|
|
71
46
|
async function nestedWithoutVite(root) {
|
|
72
47
|
const rootPkg = join(root, 'package.json')
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
* @param {string} dir каталог
|
|
76
|
-
* @returns {Promise<void>}
|
|
77
|
-
*/
|
|
78
|
-
async function walk(dir) {
|
|
79
|
-
if (result) return
|
|
80
|
-
let entries
|
|
48
|
+
for (const abs of await findAllPackageJsonPaths(root, [])) {
|
|
49
|
+
if (abs === rootPkg) continue
|
|
81
50
|
try {
|
|
82
|
-
|
|
51
|
+
const dev = JSON.parse(await readFile(abs, 'utf8'))?.devDependencies
|
|
52
|
+
const hasVite = dev && typeof dev === 'object' && !Array.isArray(dev) && Object.hasOwn(dev, 'vite')
|
|
53
|
+
if (!hasVite) return true
|
|
83
54
|
} catch {
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
for (const entry of entries) {
|
|
87
|
-
if (result) return
|
|
88
|
-
const abs = join(dir, entry.name)
|
|
89
|
-
if (entry.isDirectory()) {
|
|
90
|
-
if (!IGNORED_DIR_NAMES.has(entry.name)) await walk(abs)
|
|
91
|
-
} else if (entry.isFile() && entry.name === 'package.json' && abs !== rootPkg) {
|
|
92
|
-
try {
|
|
93
|
-
const dev = JSON.parse(await readFile(abs, 'utf8'))?.devDependencies
|
|
94
|
-
const hasVite = dev && typeof dev === 'object' && !Array.isArray(dev) && Object.hasOwn(dev, 'vite')
|
|
95
|
-
if (!hasVite) result = true
|
|
96
|
-
} catch {
|
|
97
|
-
/* пошкоджений package.json не вважаємо vite-проєктом */
|
|
98
|
-
}
|
|
99
|
-
}
|
|
55
|
+
/* пошкоджений package.json не вважаємо vite-проєктом */
|
|
100
56
|
}
|
|
101
57
|
}
|
|
102
|
-
|
|
103
|
-
return result
|
|
58
|
+
return false
|
|
104
59
|
}
|
|
105
60
|
|
|
106
61
|
/** Реєстр предикатів: імʼя → реалізація. Виклик за `meta.json.auto.predicate`. */
|
|
@@ -4,7 +4,7 @@ import { globby } from 'globby'
|
|
|
4
4
|
|
|
5
5
|
// .git ніколи не потрапляє в .gitignore — пропускаємо завжди.
|
|
6
6
|
// node_modules — safety net: проєкт може не мати .gitignore або запускатись поза git-репо.
|
|
7
|
-
const ALWAYS_IGNORE = ['.git/**', 'node_modules/**']
|
|
7
|
+
export const ALWAYS_IGNORE = ['.git/**', 'node_modules/**']
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Рекурсивно обходить каталог, поважаючи .gitignore (включно з вкладеними).
|