@nitra/cursor 1.8.212 → 1.8.216
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 +55 -6
- package/bin/auto-rules.md +2 -0
- package/mdc/js-bun-db.mdc +1 -1
- package/mdc/js-bun-redis.mdc +21 -0
- package/mdc/k8s.mdc +79 -17
- package/package.json +1 -1
- package/policy/js_bun_redis/package_json/package_json.rego +37 -0
- package/scripts/auto-rules.mjs +14 -4
- package/scripts/auto-skills.mjs +1 -7
- package/scripts/check-hasura.mjs +1 -2
- package/scripts/check-js-bun-redis.mjs +98 -0
- package/scripts/check-k8s.mjs +301 -78
- package/scripts/lint-conftest.mjs +14 -6
- package/scripts/utils/bun-sql-scan.mjs +1 -2
- package/scripts/utils/conn-file-rules.mjs +6 -2
- package/scripts/utils/redis-imports.mjs +172 -0
- package/skills/taze/SKILL.md +2 -1
|
@@ -100,7 +100,7 @@ function collectNamedExportNames(program) {
|
|
|
100
100
|
(decl.type === 'FunctionDeclaration' || decl.type === 'ClassDeclaration') &&
|
|
101
101
|
decl.id &&
|
|
102
102
|
typeof decl.id === 'object' &&
|
|
103
|
-
typeof /** @type {Record<string, unknown>} */ (decl.id).name === 'string'
|
|
103
|
+
typeof (/** @type {Record<string, unknown>} */ (decl.id).name) === 'string'
|
|
104
104
|
) {
|
|
105
105
|
out.push(/** @type {string} */ (/** @type {Record<string, unknown>} */ (decl.id).name))
|
|
106
106
|
}
|
|
@@ -128,7 +128,11 @@ function hasDefaultExport(program) {
|
|
|
128
128
|
const body = /** @type {Record<string, unknown>} */ (program).body
|
|
129
129
|
if (!Array.isArray(body)) return false
|
|
130
130
|
for (const node of body) {
|
|
131
|
-
if (
|
|
131
|
+
if (
|
|
132
|
+
node &&
|
|
133
|
+
typeof node === 'object' &&
|
|
134
|
+
/** @type {Record<string, unknown>} */ (node).type === 'ExportDefaultDeclaration'
|
|
135
|
+
) {
|
|
132
136
|
return true
|
|
133
137
|
}
|
|
134
138
|
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Знаходить імпорти з `ioredis` та `node-redis` (та підшляхів `redis/...`) у джерелах —
|
|
3
|
+
* їх треба замінити на Bun native Redis (`import { redis } from 'bun'`) згідно з
|
|
4
|
+
* `js-bun-redis.mdc` (<https://bun.com/docs/runtime/redis>).
|
|
5
|
+
*
|
|
6
|
+
* Семантика береться з **oxc-parser** (`module.staticImports`) — без regex по тілу файлу.
|
|
7
|
+
* Додатково по AST програми ловимо `require('ioredis')` і динамічний `import('ioredis')`,
|
|
8
|
+
* щоб правило працювало і у CommonJS, і при динамічному `import` у межах одного файлу.
|
|
9
|
+
*
|
|
10
|
+
* `node-redis` публікується під рядом імен:
|
|
11
|
+
* - кореневий пакет `redis` (саме так його імпортують у v4+);
|
|
12
|
+
* - історичний `node-redis` (рідше);
|
|
13
|
+
* - підпакети, які тягнуться разом: `@redis/client`, `@redis/json`, `@redis/search`,
|
|
14
|
+
* `@redis/time-series`, `@redis/bloom` — їх теж треба прибирати разом із основним
|
|
15
|
+
* клієнтом, щоб не лишилось «половини» інтеграції після переходу на Bun.
|
|
16
|
+
*
|
|
17
|
+
* Сканер не вимагає, щоб файл компілювався: при синтаксичних помилках повертається порожній
|
|
18
|
+
* результат — спочатку треба полагодити синтаксис, потім перезапустити перевірку.
|
|
19
|
+
*/
|
|
20
|
+
import { parseSync } from 'oxc-parser'
|
|
21
|
+
|
|
22
|
+
import { langFromPath, normalizeSnippet, offsetToLine } from './ast-scan-utils.mjs'
|
|
23
|
+
|
|
24
|
+
const SOURCE_FILE_RE = /\.([cm]?[jt]sx?)$/u
|
|
25
|
+
const FORBIDDEN_MODULE_NAMES = new Set([
|
|
26
|
+
'ioredis',
|
|
27
|
+
'node-redis',
|
|
28
|
+
'redis',
|
|
29
|
+
'@redis/client',
|
|
30
|
+
'@redis/json',
|
|
31
|
+
'@redis/search',
|
|
32
|
+
'@redis/time-series',
|
|
33
|
+
'@redis/bloom'
|
|
34
|
+
])
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Чи є рядок-специфікатор імпорту забороненим (`ioredis`, `node-redis`, `redis`, `redis/...`,
|
|
38
|
+
* `ioredis/...`, `@redis/<sub>`).
|
|
39
|
+
*
|
|
40
|
+
* Використовуємо префікс-збіг для `ioredis/` та `redis/` — щоб ловити підшляхи
|
|
41
|
+
* (`ioredis/built/utils`, `redis/dist/...`), але не зачепити сторонні пакети
|
|
42
|
+
* на кшталт `redis-mock`, які треба валідувати окремо.
|
|
43
|
+
* @param {string} mod рядкове значення з `import '...'` / `require('...')`
|
|
44
|
+
* @returns {boolean} true, якщо такий specifier треба викинути на користь Bun native Redis
|
|
45
|
+
*/
|
|
46
|
+
function isForbiddenRedisModule(mod) {
|
|
47
|
+
if (FORBIDDEN_MODULE_NAMES.has(mod)) return true
|
|
48
|
+
return mod.startsWith('ioredis/') || mod.startsWith('redis/') || mod.startsWith('@redis/')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Перевіряє, чи це виклик `require('<module>')` з рядковим аргументом.
|
|
53
|
+
* @param {Record<string, unknown> | null | undefined} node вузол AST
|
|
54
|
+
* @returns {string | null} ім'я модуля з аргументу, інакше `null`
|
|
55
|
+
*/
|
|
56
|
+
function requireCallModule(node) {
|
|
57
|
+
if (!node || node.type !== 'CallExpression') return null
|
|
58
|
+
const callee = node.callee
|
|
59
|
+
if (!callee || callee.type !== 'Identifier' || callee.name !== 'require') return null
|
|
60
|
+
const arg = node.arguments?.[0]
|
|
61
|
+
if (!arg || arg.type !== 'Literal' || typeof arg.value !== 'string') return null
|
|
62
|
+
return arg.value
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Перевіряє, чи це динамічний `import('<module>')` з рядковим аргументом.
|
|
67
|
+
* @param {Record<string, unknown> | null | undefined} node вузол AST
|
|
68
|
+
* @returns {string | null} ім'я модуля, інакше `null`
|
|
69
|
+
*/
|
|
70
|
+
function dynamicImportModule(node) {
|
|
71
|
+
if (!node || node.type !== 'ImportExpression') return null
|
|
72
|
+
const src = node.source
|
|
73
|
+
if (!src || src.type !== 'Literal' || typeof src.value !== 'string') return null
|
|
74
|
+
return src.value
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Простий рекурсивний обхід AST: заходимо в усі обʼєкти/масиви, щоб знайти require/import-вузли.
|
|
79
|
+
* @param {unknown} node корінь або під-вузол AST
|
|
80
|
+
* @param {(n: unknown) => void} visit виклик для кожного обʼєкта-вузла
|
|
81
|
+
* @returns {void}
|
|
82
|
+
*/
|
|
83
|
+
function walkAst(node, visit) {
|
|
84
|
+
if (!node || typeof node !== 'object') return
|
|
85
|
+
if (Array.isArray(node)) {
|
|
86
|
+
for (const item of node) walkAst(item, visit)
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
if (typeof node.type === 'string') {
|
|
90
|
+
visit(node)
|
|
91
|
+
}
|
|
92
|
+
for (const key of Object.keys(node)) {
|
|
93
|
+
if (key !== 'parent') {
|
|
94
|
+
const v = node[key]
|
|
95
|
+
if (v && typeof v === 'object') walkAst(v, visit)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Знаходить заборонені імпорти/require з `ioredis` / `node-redis` у тексті.
|
|
102
|
+
* @param {string} content вихідний код
|
|
103
|
+
* @param {string} [virtualPath] шлях для вибору `lang` (наприклад `pkg/src/foo.ts`)
|
|
104
|
+
* @returns {{ line: number, snippet: string, module: string }[]} список порушень
|
|
105
|
+
*/
|
|
106
|
+
export function findRedisImportsInText(content, virtualPath = 'scan.ts') {
|
|
107
|
+
const pathForLang = virtualPath || 'scan.ts'
|
|
108
|
+
const lang = langFromPath(pathForLang)
|
|
109
|
+
let result
|
|
110
|
+
try {
|
|
111
|
+
result = parseSync(pathForLang, content, { lang, sourceType: 'module' })
|
|
112
|
+
} catch {
|
|
113
|
+
return []
|
|
114
|
+
}
|
|
115
|
+
if (result.errors?.length) {
|
|
116
|
+
return []
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** @type {{ line: number, snippet: string, module: string }[]} */
|
|
120
|
+
const out = []
|
|
121
|
+
|
|
122
|
+
for (const imp of result.module?.staticImports ?? []) {
|
|
123
|
+
const mod = imp.moduleRequest?.value
|
|
124
|
+
if (typeof mod === 'string' && isForbiddenRedisModule(mod)) {
|
|
125
|
+
out.push({
|
|
126
|
+
line: offsetToLine(content, imp.start),
|
|
127
|
+
snippet: normalizeSnippet(content.slice(imp.start, imp.end)),
|
|
128
|
+
module: mod
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
walkAst(result.program, node => {
|
|
134
|
+
const reqMod = requireCallModule(node)
|
|
135
|
+
if (reqMod && isForbiddenRedisModule(reqMod)) {
|
|
136
|
+
out.push({
|
|
137
|
+
line: offsetToLine(content, node.start),
|
|
138
|
+
snippet: normalizeSnippet(content.slice(node.start, node.end)),
|
|
139
|
+
module: reqMod
|
|
140
|
+
})
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
const dynMod = dynamicImportModule(node)
|
|
144
|
+
if (dynMod && isForbiddenRedisModule(dynMod)) {
|
|
145
|
+
out.push({
|
|
146
|
+
line: offsetToLine(content, node.start),
|
|
147
|
+
snippet: normalizeSnippet(content.slice(node.start, node.end)),
|
|
148
|
+
module: dynMod
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
return out
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Чи сканувати цей файл за розширенням (JS/TS-сімʼя).
|
|
158
|
+
* @param {string} relativePath відносний шлях до файлу
|
|
159
|
+
* @returns {boolean} `true`, якщо розширення підходить для пошуку імпорту
|
|
160
|
+
*/
|
|
161
|
+
export function isRedisScanSourceFile(relativePath) {
|
|
162
|
+
return SOURCE_FILE_RE.test(relativePath)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Чи слід пропустити файл під час обходу пакета (декларації типів — лише типи, не виконувані).
|
|
167
|
+
* @param {string} relativePosix шлях з posix-слешами
|
|
168
|
+
* @returns {boolean} `true`, якщо файл не сканувати
|
|
169
|
+
*/
|
|
170
|
+
export function shouldSkipFileForRedisScan(relativePosix) {
|
|
171
|
+
return relativePosix.endsWith('.d.ts')
|
|
172
|
+
}
|
package/skills/taze/SKILL.md
CHANGED
|
@@ -13,7 +13,7 @@ description: >-
|
|
|
13
13
|
|
|
14
14
|
## Передумови
|
|
15
15
|
|
|
16
|
-
- Чисте робоче дерево (`git status` без
|
|
16
|
+
- Чисте робоче дерево (`git status` без незакомічених змін у `package.json` / `bun.lock` / `node_modules`) — інакше різницю не відрізнити від оновлення.
|
|
17
17
|
- Встановлений `bun` і доступний `bunx`.
|
|
18
18
|
- Запуск з кореня проекту (де лежить `package.json` / `bun.lock`).
|
|
19
19
|
|
|
@@ -66,6 +66,7 @@ rg -n "<імпорт|функція|опція>" --type ts --type js --type vue
|
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
Класифікувати:
|
|
69
|
+
|
|
69
70
|
- **сумісно** — проект не використовує зачеплене API → нічого не робити.
|
|
70
71
|
- **несумісно** — використання знайдено → перейти до п. 6.
|
|
71
72
|
|