@nitra/cursor 3.22.0 → 3.23.1
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/.pi-template/extensions/n-cursor-adr/docs/index.md +181 -0
- package/AGENTS.template.md +4 -0
- package/CHANGELOG.md +37 -3
- package/bin/docs/n-cursor.md +636 -0
- package/bin/docs/rename-yaml-extensions.md +207 -0
- package/bin/n-cursor.js +30 -3
- package/package.json +1 -1
- package/rules/abie/docs/fix.md +18 -0
- package/rules/abie/js/docs/applies.md +26 -0
- package/rules/abie/js/docs/env_dns.md +32 -0
- package/rules/abie/js/docs/firebase_hosting.md +23 -0
- package/rules/abie/js/docs/hc_pairing.md +35 -0
- package/rules/abie/js/docs/ua_http_route.md +28 -0
- package/rules/abie/js/docs/ua_node_selector.md +28 -0
- package/rules/abie/lib/docs/enabled.md +29 -0
- package/rules/abie/lib/docs/env-dns.md +35 -0
- package/rules/abie/lib/docs/hc-yaml.md +33 -0
- package/rules/abie/lib/docs/http-route.md +44 -0
- package/rules/abie/lib/docs/k8s-tree.md +40 -0
- package/rules/abie/lib/docs/kustomization-patches.md +47 -0
- package/rules/abie/lib/docs/overlay-paths.md +38 -0
- package/rules/abie/lib/docs/yaml.md +29 -0
- package/rules/adr/docs/fix.md +148 -0
- package/rules/adr/js/docs/hooks.md +259 -0
- package/rules/bun/docs/fix.md +156 -0
- package/rules/bun/js/docs/layout.md +393 -0
- package/rules/capacitor/docs/fix.md +121 -0
- package/rules/capacitor/js/docs/platforms.md +295 -0
- package/rules/changelog/changelog.mdc +4 -2
- package/rules/changelog/docs/fix.md +174 -0
- package/rules/changelog/js/consistency.mjs +114 -13
- package/rules/changelog/js/docs/consistency.md +387 -0
- package/rules/changelog/lib/docs/package-manifest.md +210 -0
- package/rules/ci4/docs/fix.md +179 -0
- package/rules/ci4/js/docs/marksman_config.md +128 -0
- package/rules/docker/docker.mdc +8 -3
- package/rules/docker/docs/fix.md +171 -0
- package/rules/docker/js/docs/lint.md +258 -0
- package/rules/docker/lib/docs/docker-hadolint.md +184 -0
- package/rules/docker/lib/docs/docker-mirror.md +247 -0
- package/rules/docker/lib/docs/docker-native-addon.md +170 -0
- package/rules/docker/lib/docs/docker-nginx-user.md +219 -0
- package/rules/docker/lint/docs/lint.md +193 -0
- package/rules/efes/docs/fix.md +203 -0
- package/rules/feedback/docs/fix.md +140 -0
- package/rules/flow/docs/fix.md +152 -0
- package/rules/ga/docs/fix.md +158 -0
- package/rules/ga/js/docs/lint.md +100 -0
- package/rules/ga/js/docs/workflows.md +217 -0
- package/rules/ga/lint/docs/lint.md +209 -0
- package/rules/ga/policy/clean_merged_branch/clean_merged_branch.rego +11 -2
- package/rules/ga/policy/clean_merged_branch/template/clean-merged-branch.yml.snippet.yml +3 -4
- package/rules/graphql/docs/fix.md +126 -0
- package/rules/graphql/js/docs/tooling.md +264 -0
- package/rules/graphql/lib/docs/graphql-gql-scan.md +219 -0
- package/rules/hasura/docs/fix.md +120 -0
- package/rules/hasura/hasura.mdc +14 -0
- package/rules/hasura/js/docs/internal_urls.md +326 -0
- package/rules/image-avif/docs/fix.md +132 -0
- package/rules/image-avif/js/docs/avif_generation.md +241 -0
- package/rules/image-compress/docs/fix.md +150 -0
- package/rules/image-compress/js/docs/package_setup.md +191 -0
- package/rules/js-bun-db/docs/fix.md +148 -0
- package/rules/js-bun-db/js/docs/safety.md +231 -0
- package/rules/js-bun-db/js-bun-db.mdc +42 -13
- package/rules/js-bun-db/lib/docs/bun-sql-scan.md +347 -0
- package/rules/js-bun-redis/docs/fix.md +123 -0
- package/rules/js-bun-redis/js/docs/imports.md +176 -0
- package/rules/js-bun-redis/lib/docs/redis-imports.md +223 -0
- package/rules/js-lint/docs/fix.md +117 -0
- package/rules/js-lint/js/docs/lint.md +250 -0
- package/rules/js-lint/js/docs/tooling.md +348 -0
- package/rules/js-lint/js/docs/utils_imports.md +207 -0
- package/rules/js-lint-ci/docs/fix.md +154 -0
- package/rules/js-lint-ci/js/docs/lint.md +144 -0
- package/rules/js-mssql/docs/fix.md +128 -0
- package/rules/js-mssql/js/docs/deps.md +263 -0
- package/rules/js-mssql/lib/docs/mssql-pool-scan.md +367 -0
- package/rules/js-run/docs/fix.md +144 -0
- package/rules/js-run/js/docs/runtime.md +388 -0
- package/rules/js-run/lib/docs/bunyan-imports.md +117 -0
- package/rules/js-run/lib/docs/check-env-scan.md +433 -0
- package/rules/js-run/lib/docs/conn-file-rules.md +300 -0
- package/rules/js-run/lib/docs/conn-imports-scan.md +204 -0
- package/rules/js-run/lib/docs/promise-settimeout-scan.md +326 -0
- package/rules/k8s/docs/fix.md +129 -0
- package/rules/k8s/js/docs/manifests.md +344 -0
- package/rules/k8s/js/manifests.mjs +6 -2
- package/rules/k8s/k8s.mdc +4 -2
- package/rules/k8s/lint/docs/lint.md +411 -0
- package/rules/k8s/policy/network_policy/template/deployment.snippet.yaml +2 -0
- package/rules/k8s/policy/network_policy/template/stateful-set.snippet.yaml +2 -0
- package/rules/nginx-default-tpl/docs/fix.md +124 -0
- package/rules/nginx-default-tpl/js/docs/template.md +378 -0
- package/rules/npm-module/docs/fix.md +98 -0
- package/rules/npm-module/js/docs/package_structure.md +274 -0
- package/rules/npm-module/js/docs/rule_meta.md +137 -0
- package/rules/npm-module/js/docs/skill_meta.md +190 -0
- package/rules/php/docs/fix.md +107 -0
- package/rules/php/js/docs/tooling.md +152 -0
- package/rules/php/lint/docs/lint.md +215 -0
- package/rules/python/docs/fix.md +163 -0
- package/rules/python/js/docs/applies.md +108 -0
- package/rules/python/js/docs/tooling.md +153 -0
- package/rules/python/lint/docs/lint.md +322 -0
- package/rules/rego/docs/fix.md +121 -0
- package/rules/rego/js/docs/applies.md +174 -0
- package/rules/rego/js/docs/lint.md +118 -0
- package/rules/rego/lint/docs/lint.md +204 -0
- package/rules/release/docs/change.md +185 -0
- package/rules/release/docs/fix.md +119 -0
- package/rules/release/docs/release.md +222 -0
- package/rules/release/lib/docs/aggregate.md +246 -0
- package/rules/release/lib/docs/change-file.md +200 -0
- package/rules/release/lib/docs/fallback.md +203 -0
- package/rules/rust/docs/fix.md +129 -0
- package/rules/rust/js/docs/applies.md +140 -0
- package/rules/rust/lib/docs/has-cargo-toml.md +130 -0
- package/rules/security/docs/fix.md +86 -0
- package/rules/security/js/docs/lint.md +171 -0
- package/rules/security/js/docs/sample_secret.md +190 -0
- package/rules/security/js/docs/trufflehog.md +137 -0
- package/rules/security/js/lint.mjs +9 -1
- package/rules/style-lint/docs/fix.md +155 -0
- package/rules/style-lint/js/docs/lint.md +184 -0
- package/rules/style-lint/js/docs/tooling.md +194 -0
- package/rules/tauri/docs/fix.md +158 -0
- package/rules/tauri/js/docs/cargo_mutants_config.md +168 -0
- package/rules/tauri/js/docs/tooling.md +228 -0
- package/rules/test/coverage/coverage.mjs +15 -3
- package/rules/test/docs/fix.md +132 -0
- package/rules/test/js/data/stryker_config/docs/stryker-vue-macros-ignorer.md +138 -0
- package/rules/test/js/data/stryker_config/docs/stryker.config.baseline.md +134 -0
- package/rules/test/js/data/stryker_config/docs/stryker.config.vue.baseline.md +160 -0
- package/rules/test/js/data/vitest_config/docs/vitest.config.baseline.md +195 -0
- package/rules/test/js/docs/cargo_mutants_config.md +173 -0
- package/rules/test/js/docs/location.md +136 -0
- package/rules/test/js/docs/no-process-chdir.md +160 -0
- package/rules/test/js/docs/no-relative-fs-path.md +271 -0
- package/rules/test/js/docs/stryker_config.md +152 -0
- package/rules/test/js/docs/vitest-config-pool-forks.md +174 -0
- package/rules/text/docs/fix.md +118 -0
- package/rules/text/js/docs/forbidden-prettier.md +143 -0
- package/rules/text/js/docs/formatting.md +256 -0
- package/rules/text/js/docs/lint.md +122 -0
- package/rules/text/lint/docs/lint.md +220 -0
- package/rules/text/lint/docs/run-dotenv-linter.md +157 -0
- package/rules/text/lint/docs/run-shellcheck.md +212 -0
- package/rules/text/lint/docs/run-v8r.md +197 -0
- package/rules/vue/docs/fix.md +127 -0
- package/rules/vue/js/docs/packages.md +335 -0
- package/rules/vue/lib/docs/vue-forbidden-imports.md +261 -0
- package/rules/worktree/docs/fix.md +161 -0
- package/schemas/rule-meta.json +5 -1
- package/scripts/auto-rules.mjs +7 -4
- package/scripts/coverage-classify/docs/apply.md +202 -0
- package/scripts/coverage-classify/docs/cache.md +203 -0
- package/scripts/coverage-classify/docs/index.md +218 -0
- package/scripts/coverage-classify/docs/prompt.md +132 -0
- package/scripts/coverage-classify/docs/verdict-schema.md +169 -0
- package/scripts/coverage-fix-extract.mjs +122 -0
- package/scripts/coverage-fix.mjs +1 -1
- package/scripts/dispatcher/docs/graph.md +346 -0
- package/scripts/dispatcher/docs/index.md +236 -0
- package/scripts/dispatcher/docs/trace.md +296 -0
- package/scripts/dispatcher/index.mjs +1 -1
- package/scripts/dispatcher/lib/active.mjs +4 -8
- package/scripts/dispatcher/lib/commands.mjs +7 -11
- package/scripts/dispatcher/lib/docs/active.md +348 -0
- package/scripts/dispatcher/lib/docs/artifact.md +232 -0
- package/scripts/dispatcher/lib/docs/budget.md +167 -0
- package/scripts/dispatcher/lib/docs/capability.md +196 -0
- package/scripts/dispatcher/lib/docs/commands.md +210 -0
- package/scripts/dispatcher/lib/docs/events.md +182 -0
- package/scripts/dispatcher/lib/docs/executor.md +190 -0
- package/scripts/dispatcher/lib/docs/flow-lock.md +161 -0
- package/scripts/dispatcher/lib/docs/flow-resolve.md +267 -0
- package/scripts/dispatcher/lib/docs/gate.md +231 -0
- package/scripts/dispatcher/lib/docs/level.md +335 -0
- package/scripts/dispatcher/lib/docs/plan-panel.md +181 -0
- package/scripts/dispatcher/lib/docs/plan.md +200 -0
- package/scripts/dispatcher/lib/docs/planner.md +269 -0
- package/scripts/dispatcher/lib/docs/review.md +255 -0
- package/scripts/dispatcher/lib/docs/reviewer.md +240 -0
- package/scripts/dispatcher/lib/docs/snapshot.md +247 -0
- package/scripts/dispatcher/lib/docs/spec.md +203 -0
- package/scripts/dispatcher/lib/docs/state-store.md +303 -0
- package/scripts/dispatcher/lib/docs/subagent-runner.md +173 -0
- package/scripts/dispatcher/lib/executor.mjs +6 -1
- package/scripts/dispatcher/lib/flow-resolve.mjs +3 -1
- package/scripts/dispatcher/lib/level.mjs +29 -3
- package/scripts/dispatcher/lib/review.mjs +1 -1
- package/scripts/dispatcher/lib/subagent-runner.mjs +5 -3
- package/scripts/docs/auto-rules.md +376 -0
- package/scripts/docs/auto-skills.md +173 -0
- package/scripts/docs/build-agents-commands.md +183 -0
- package/scripts/docs/cli-entry.md +153 -0
- package/scripts/docs/coverage-fix.md +177 -0
- package/scripts/docs/ensure-nitra-cursor-dev-dependencies.md +189 -0
- package/scripts/lib/changed-files.mjs +4 -1
- package/scripts/lib/docs/changed-files.md +149 -0
- package/scripts/lib/docs/check-mdc-template-refs.md +222 -0
- package/scripts/lib/docs/check-reporter.md +175 -0
- package/scripts/lib/docs/discover-check-rules-from-cursor.md +157 -0
- package/scripts/lib/docs/discover-checkable-rules.md +165 -0
- package/scripts/lib/docs/ensure-tool.md +254 -0
- package/scripts/lib/docs/generated-markdown.md +275 -0
- package/scripts/lib/docs/gha-workflow.md +326 -0
- package/scripts/lib/docs/inline-template-links.md +303 -0
- package/scripts/lib/docs/list-rule-ids.md +156 -0
- package/scripts/lib/docs/load-cursor-config.md +147 -0
- package/scripts/lib/docs/mirror-parity.md +167 -0
- package/scripts/lib/worktree.mjs +26 -0
- package/scripts/worktree-cli.mjs +12 -2
- package/skills/coverage-fix/SKILL.md +34 -45
- package/skills/docgen/SKILL.md +44 -23
- package/skills/docgen/bench/etalon/firebase_hosting.md +19 -0
- package/skills/docgen/bench/etalon/k8s-tree.md +24 -0
- package/skills/docgen/bench/etalon/overlay-paths.md +24 -0
- package/skills/docgen/js/docgen-ignore.mjs +54 -0
- package/skills/docgen/js/docgen-scan.mjs +37 -21
- package/skills/llm-patch/SKILL.md +23 -2
- package/skills/start-check/SKILL.md +26 -53
- package/skills/start-check/js/check.mjs +211 -0
- package/skills/taze/SKILL.md +9 -3
- package/skills/taze/js/diff.mjs +154 -0
- package/types/bin/n-cursor.d.ts +1 -1
- package/skills/fix-tests/SKILL.md +0 -119
- package/skills/fix-tests/meta.json +0 -1
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# `fix.mjs` — entry-point правила `js-bun-db`
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Файл `npm/rules/js-bun-db/fix.mjs` — це **точка входу** (entry-point) для правила `js-bun-db` пакета `@nitra/cursor`. Правило перевіряє/виправляє код, що працює з базою даних у середовищі Bun (JavaScript runtime), і виконується у стандартному CI4-пайплайні:
|
|
6
|
+
|
|
7
|
+
1. `applies` — визначає, до яких файлів застосовується правило;
|
|
8
|
+
2. `JS-concerns` — перевіряє/чинить специфічні JS-аспекти (через стандартний пайплайн);
|
|
9
|
+
3. `policy` — застосовує політики правила (наприклад, заборонені патерни, переписування коду);
|
|
10
|
+
4. `mdc-refs` — оновлює перехресні посилання у `.mdc`-документах (Markdown Cursor).
|
|
11
|
+
|
|
12
|
+
Файл виконує **дві ролі одночасно**:
|
|
13
|
+
|
|
14
|
+
- **Library mode** — експортує функцію `run(ctx)`, яку викликає зовнішній orchestrator (CLI `@nitra/cursor`, ESLint-обгортка, інші правила) для виконання пайплайну у спільному процесі (з можливістю переюзу `walkCache` через переданий `ctx`).
|
|
15
|
+
- **Standalone mode** — якщо файл запущено напряму як CLI (`bun rules/js-bun-db/fix.mjs`), він проганяє повний CLI-цикл (`runRuleCli`) із завантаженням конфігурації, whitelist-фільтрацією та підсумковим виводом, після чого виставляє відповідний `process.exit(code)` для CI/IDE.
|
|
16
|
+
|
|
17
|
+
Файл свідомо мінімалістичний: уся реальна логіка винесена у бібліотечні утиліти (`run-standard-rule.mjs`, `run-rule-cli.mjs`); `fix.mjs` лише делегує виклики, передаючи `import.meta.dirname` як ідентифікатор поточної директорії правила.
|
|
18
|
+
|
|
19
|
+
## Експорти / API
|
|
20
|
+
|
|
21
|
+
| Експорт | Тип | Призначення |
|
|
22
|
+
| ------- | ---------- | ---------------------------------------------------------------------------------------- |
|
|
23
|
+
| `run` | `function` | Library-режим: виконує стандартний пайплайн правила для переданого/дефолтного контексту. |
|
|
24
|
+
|
|
25
|
+
Інших іменованих експортів немає. Файл також містить **side-effect-блок** верхнього рівня (див. секцію [Потік виконання](#потік-виконання--використання)), який спрацьовує лише за умови запуску модуля як CLI.
|
|
26
|
+
|
|
27
|
+
## Функції
|
|
28
|
+
|
|
29
|
+
### `run(ctx)`
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
export function run(ctx)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Призначення.** Запускає стандартний пайплайн правила (`applies → JS-concerns → policy → mdc-refs`) для директорії, у якій лежить сам `fix.mjs`. Тобто — для правила `js-bun-db`. Делегує всю роботу функції `runStandardRule`.
|
|
36
|
+
|
|
37
|
+
**Параметри.**
|
|
38
|
+
|
|
39
|
+
| Ім'я | Тип | Обов'язковість | Опис |
|
|
40
|
+
| ----- | ----------------------------------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
41
|
+
| `ctx` | `RuleContext` (з `../../scripts/lib/run-standard-rule.mjs`) | Опціональний | Контекст прогону (наприклад, `walkCache` — закешований обхід файлової системи, поділюваний між правилами одного CLI-сеансу). Якщо не передано — `runStandardRule` створює свій. |
|
|
42
|
+
|
|
43
|
+
**Повертає.** `Promise<number>`:
|
|
44
|
+
|
|
45
|
+
- `0` — правило відпрацювало успішно, порушень немає (або всі виправлені автоматично, якщо це fix-режим);
|
|
46
|
+
- `1` — виявлено порушення, які не вдалося виправити автоматично.
|
|
47
|
+
|
|
48
|
+
**Side effects.**
|
|
49
|
+
|
|
50
|
+
- Може **читати** файли проєкту (через walk у `runStandardRule`).
|
|
51
|
+
- Може **записувати/змінювати** файли проєкту, якщо стандартний пайплайн застосовує автоматичні фіксери.
|
|
52
|
+
- Може **писати у stdout/stderr** діагностичні повідомлення стандартного пайплайну.
|
|
53
|
+
- **Не** викликає `process.exit` — рішення про exit-code залишається за викликачем.
|
|
54
|
+
|
|
55
|
+
**Ключовий момент.** Перший аргумент `runStandardRule` — `import.meta.dirname` — це абсолютний шлях до директорії, у якій лежить `fix.mjs` (тобто `…/npm/rules/js-bun-db/`). За цим шляхом `runStandardRule` визначає **id правила** і завантажує його `.mdc`, `check-*.mjs`, тощо.
|
|
56
|
+
|
|
57
|
+
## Залежності
|
|
58
|
+
|
|
59
|
+
### Внутрішні модулі (npm/scripts/lib)
|
|
60
|
+
|
|
61
|
+
| Імпорт | Звідки | Роль |
|
|
62
|
+
| ----------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
63
|
+
| `isRunAsCli` | `../../scripts/lib/run-rule-cli.mjs` | Утиліта-детектор: повертає `true`, якщо переданий `import.meta.url` відповідає файлу, який запущено як головний модуль (`process.argv[1]`). Дозволяє відрізнити library-import від standalone-запуску. |
|
|
64
|
+
| `runRuleCli` | `../../scripts/lib/run-rule-cli.mjs` | Standalone CLI orchestration: завантажує конфіг (`.cursor.json`/`package.json`), застосовує whitelist, запускає правило з директорії, друкує summary і повертає `Promise<number>` (exit-code). |
|
|
65
|
+
| `runStandardRule` | `../../scripts/lib/run-standard-rule.mjs` | Бібліотечний прогон стандартного пайплайну правила (`applies → JS-concerns → policy → mdc-refs`). Не виконує CLI-обв'язку (конфіг, summary). |
|
|
66
|
+
|
|
67
|
+
### Системні API
|
|
68
|
+
|
|
69
|
+
| API | Використання |
|
|
70
|
+
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
71
|
+
| `import.meta.dirname` | Абсолютний шлях до директорії, що містить цей `fix.mjs`. Передається у `runStandardRule` та `runRuleCli` як ідентифікатор правила. |
|
|
72
|
+
| `import.meta.url` | URL поточного модуля. Передається в `isRunAsCli` для перевірки, чи запущено модуль як CLI. |
|
|
73
|
+
| `process.exit` | Викликається лише у standalone-гілці з кодом, поверненим `runRuleCli`. Дає змогу CI/IDE отримати exit-status. |
|
|
74
|
+
|
|
75
|
+
### ESLint-директива
|
|
76
|
+
|
|
77
|
+
Файл містить рядкову директиву:
|
|
78
|
+
|
|
79
|
+
```text
|
|
80
|
+
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Свідоме вимкнення правил `n/no-process-exit` та `unicorn/no-process-exit` для рядка з `process.exit`. Виправдання: standalone entry-point **зобов'язаний** повертати exit-code для CI/IDE-консьюмерів.
|
|
84
|
+
|
|
85
|
+
## Потік виконання / Використання
|
|
86
|
+
|
|
87
|
+
### Дві ролі модуля
|
|
88
|
+
|
|
89
|
+
Файл проектувався так, щоб **той самий код** обслуговував і library-, і standalone-сценарії, без дублювання логіки.
|
|
90
|
+
|
|
91
|
+
#### Library mode (import + run)
|
|
92
|
+
|
|
93
|
+
```js
|
|
94
|
+
import { run } from '@nitra/cursor/rules/js-bun-db/fix.mjs'
|
|
95
|
+
|
|
96
|
+
const exitCode = await run({ walkCache })
|
|
97
|
+
// ^^^^^^^^ 0 = OK, 1 = порушення
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
- Викликач (наприклад, головний CLI `@nitra/cursor`) уже завантажив конфіг, побудував список правил, створив спільний `walkCache`.
|
|
101
|
+
- Викликає `run(ctx)`, отримує число (exit-code-семантика), сам приймає рішення про `process.exit`.
|
|
102
|
+
- Гілка `if (isRunAsCli(...))` **не виконується**, бо `import.meta.url` цього файлу не збігається з URL головного модуля процесу.
|
|
103
|
+
|
|
104
|
+
#### Standalone mode (bun rules/js-bun-db/fix.mjs)
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
bun npm/rules/js-bun-db/fix.mjs
|
|
108
|
+
# еквівалентно:
|
|
109
|
+
npx @nitra/cursor fix js-bun-db
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
- Bun запускає `fix.mjs` як головний модуль.
|
|
113
|
+
- `isRunAsCli(import.meta.url)` повертає `true`.
|
|
114
|
+
- Виконується `await runRuleCli(import.meta.dirname)` — повний CLI-цикл: завантаження конфігурації, whitelist-фільтрація, прогон, друк підсумку.
|
|
115
|
+
- Результат (number) передається у `process.exit`, який завершує процес із потрібним exit-code для CI/IDE.
|
|
116
|
+
|
|
117
|
+
### Послідовність кроків при standalone-запуску
|
|
118
|
+
|
|
119
|
+
1. Інтерпретатор виконує верхньорівневі імпорти (`isRunAsCli`, `runRuleCli`, `runStandardRule`).
|
|
120
|
+
2. Створюється/реєструється функція `run` (експорт).
|
|
121
|
+
3. Перевіряється `isRunAsCli(import.meta.url)` — для standalone дає `true`.
|
|
122
|
+
4. Викликається `await runRuleCli(import.meta.dirname)` — повертає `Promise<number>`.
|
|
123
|
+
5. `process.exit(<number>)` завершує процес.
|
|
124
|
+
|
|
125
|
+
### Послідовність кроків при library-import
|
|
126
|
+
|
|
127
|
+
1. Інтерпретатор виконує верхньорівневі імпорти.
|
|
128
|
+
2. Створюється функція `run` (експорт стає доступним викликачу).
|
|
129
|
+
3. `isRunAsCli(...)` повертає `false` — гілка `if (...)` пропускається.
|
|
130
|
+
4. Модуль готовий: викликач у будь-який момент викликає `run(ctx)`.
|
|
131
|
+
|
|
132
|
+
### Архітектурні нотатки
|
|
133
|
+
|
|
134
|
+
- **Чому два режими в одному файлі.** Така подвійна роль (`library + standalone`) — конвенція пакета `@nitra/cursor` для всіх `fix.mjs` правил. Це дає:
|
|
135
|
+
- можливість швидко прогнати **одне правило** локально під час розробки (`bun rules/<id>/fix.mjs`);
|
|
136
|
+
- можливість CLI-оркестратору **імпортувати** правило без форку процесу;
|
|
137
|
+
- єдиний контракт (`run(ctx) → Promise<number>`) для всіх правил пакета.
|
|
138
|
+
- **Чому `import.meta.dirname`.** Це абсолютний шлях, який не залежить від CWD виклику; правило знаходить свої артефакти (`.mdc`, `check-*.mjs`, `meta.json`) відносно власного розташування на диску.
|
|
139
|
+
- **Чому `await` перед `runRuleCli`.** `runRuleCli` — асинхронна функція; `process.exit` має отримати готове число, а не `Promise`.
|
|
140
|
+
|
|
141
|
+
## Rebuild Test
|
|
142
|
+
|
|
143
|
+
З опису вище можна реконструювати поведінку файлу:
|
|
144
|
+
|
|
145
|
+
1. **Імпорти.** Дві утиліти з `../../scripts/lib/run-rule-cli.mjs` (`isRunAsCli`, `runRuleCli`) та одна з `../../scripts/lib/run-standard-rule.mjs` (`runStandardRule`).
|
|
146
|
+
2. **Експорт `run(ctx)`.** Повертає `runStandardRule(import.meta.dirname, ctx)`. Сигнатура: `(ctx?: RuleContext) => Promise<number>`. `0` — OK, `1` — порушення.
|
|
147
|
+
3. **CLI-блок.** Умова `if (isRunAsCli(import.meta.url))`. Усередині — `process.exit(await runRuleCli(import.meta.dirname))`. Перед `process.exit` стоїть `eslint-disable-next-line` для `n/no-process-exit` та `unicorn/no-process-exit` із поясненням, що standalone entry-point має повертати exit-code.
|
|
148
|
+
4. **Жодної іншої логіки.** Файл не містить ні констант, ні класів, ні допоміжних функцій — лише делегування.
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# `safety.mjs` — перевірка правила `js-bun-db.mdc`
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Модуль `npm/rules/js-bun-db/js/safety.mjs` — основний JS-чекер правила `js-bun-db.mdc`. Його завдання — обійти репозиторій і переконатися, що проєкт використовує **Bun native SQL** (`import { sql, SQL } from 'bun'`) замість застарілих PostgreSQL/MySQL клієнтів, та що Bun SQL використовується безпечно.
|
|
6
|
+
|
|
7
|
+
Чекер виконує три великі групи перевірок:
|
|
8
|
+
|
|
9
|
+
1. **Заборона `pg-format` / `mysql2`** у `dependencies` будь-якого `package.json`. Цю частину закриває окремий Rego-поліс у `npm/policy/js_bun_db/package_json/` — `safety.mjs` лише довіряє йому й не дублює перевірку.
|
|
10
|
+
2. **Виключення для `pg`** — dependency `pg` дозволено тільки тоді, коли в коді справді використовується `LISTEN` / `NOTIFY` / `UNLISTEN` або listener `.on('notification', ...)` (Bun SQL поки не покриває LISTEN/NOTIFY). Перевірка йде на двох рівнях: на рівні кожного `package.json` (якщо в проекті взагалі немає LISTEN/NOTIFY — `pg` забороняється) і per-file (файл з `import 'pg'` сам має містити LISTEN/NOTIFY-патерн).
|
|
11
|
+
3. **Безпечне використання Bun SQL** у файлах з `import { sql|SQL } from 'bun'`. Сюди входять перевірки на `new SQL(...)` у функції (має бути модульний singleton), `sql.unsafe(...)` без маркера-коментаря `// allow-unsafe: <reason>`, динамічні `.join(',')` у `IN (...)` / `VALUES (...)`, IN-списки без guard на пустоту, pg-format-сумісні шими (`format` / `quoteLiteral` / `quoteIdent`) і `query(text, params)`-обгортки над `<obj>.unsafe(...)`.
|
|
12
|
+
|
|
13
|
+
Усі знайдені порушення повідомляються через `createCheckReporter()` як `fail`, а кожна «чиста» категорія дає окремий `pass`. Підсумкове значення — `0` (все чисто) або `1` (є порушення) — повертає функція `check`.
|
|
14
|
+
|
|
15
|
+
## Експорти / API
|
|
16
|
+
|
|
17
|
+
| Експорт | Тип | Призначення |
|
|
18
|
+
| ------------- | ---------------- | ---------------------------------------------------------------------------------------------------- |
|
|
19
|
+
| `check(cwd?)` | `async function` | Публічна точка входу. Виконує всі перевірки правила `js-bun-db.mdc` і повертає exit-код (`0` / `1`). |
|
|
20
|
+
|
|
21
|
+
Решта функцій (`findAllSourcePathsForBunSqlScan`, `scanSourcesForBunSqlPatterns`, `collectPgUsageForFile`, `scanFileForBunSqlPatterns`, `checkPgDependencyAndUsage`, `messageForBunSqlInListGuard`) — внутрішні; з модуля не експортуються.
|
|
22
|
+
|
|
23
|
+
Константи модульного скоупу (теж не експортуються):
|
|
24
|
+
|
|
25
|
+
- `LISTEN_NOTIFY_KEYWORD_RE` — `/\b(LISTEN|UNLISTEN|NOTIFY)\b/iu`. Дешевий pre-filter regex для пошуку SQL-ключових слів.
|
|
26
|
+
- `NOTIFICATION_LITERAL_RE` — `/['"`]notification['"`]/u`. Дешевий pre-filter regex для рядкового літерала `'notification'`(як ім'я події в`.on('notification', ...)`).
|
|
27
|
+
|
|
28
|
+
Обидві винесені у модульний скоуп, щоб не перекомпілювати `RegExp` на кожен виклик `collectPgUsageForFile`.
|
|
29
|
+
|
|
30
|
+
## Функції
|
|
31
|
+
|
|
32
|
+
### `findAllSourcePathsForBunSqlScan(repoRoot, ignorePaths)`
|
|
33
|
+
|
|
34
|
+
- **Сигнатура:** `async function findAllSourcePathsForBunSqlScan(repoRoot: string, ignorePaths: string[]): Promise<string[]>`
|
|
35
|
+
- **Параметри:**
|
|
36
|
+
- `repoRoot` — абсолютний шлях до кореня репозиторію.
|
|
37
|
+
- `ignorePaths` — масив абсолютних шляхів каталогів, які повністю виключаються з обходу (звичайно отримується з `loadCursorIgnorePaths`).
|
|
38
|
+
- **Повертає:** масив абсолютних шляхів файлів, що проходять `isBunSqlScanSourceFile(rel)`, відсортований за відносним posix-шляхом (через `localeCompare`).
|
|
39
|
+
- **Side effects:** виключно читання директорій через `walkDir`; нічого не пише.
|
|
40
|
+
- **Деталі:** використовує `walkDir` із зовнішнього колбека: для кожного знайденого `absPath` обчислює відносний шлях, нормалізує windows-роздільники (`\\` → `/`) і викликає `isBunSqlScanSourceFile`. Сортування потрібне, щоб порядок повідомлень про порушення був детермінованим.
|
|
41
|
+
|
|
42
|
+
### `scanSourcesForBunSqlPatterns(sourcePaths, repoRoot, reporter)`
|
|
43
|
+
|
|
44
|
+
- **Сигнатура:** `async function scanSourcesForBunSqlPatterns(sourcePaths: string[], repoRoot: string, reporter: { pass: (m: string) => void, fail: (m: string) => void }): Promise<{ hasBunSqlImport: boolean, perRequest: number, unsafeCall: number, unsafeTemplateInterp: number, dynamicList: number, inListGuard: number, pgLeftover: number, pgFormatShim: number, queryWrapper: number, pgUsage: Array<{ rel: string, imports: { line: number, snippet: string }[], listenNotify: { line: number, snippet: string, kind: string }[] }> }>`
|
|
45
|
+
- **Параметри:**
|
|
46
|
+
- `sourcePaths` — абсолютні шляхи джерел, відібраних `findAllSourcePathsForBunSqlScan`.
|
|
47
|
+
- `repoRoot` — абсолютний шлях до кореня.
|
|
48
|
+
- `reporter` — обʼєкт з `pass`/`fail` (тут використовується лише `fail`).
|
|
49
|
+
- **Повертає:** обʼєкт з прапором `hasBunSqlImport`, лічильниками порушень за категоріями (`perRequest`, `unsafeCall`, `unsafeTemplateInterp`, `dynamicList`, `inListGuard`, `pgLeftover`, `pgFormatShim`, `queryWrapper`) та масивом `pgUsage` (тільки файли з імпортом `'pg'` або LISTEN/NOTIFY-патерном).
|
|
50
|
+
- **Side effects:** читає файли через `readFile`; викликає `fail(...)` для кожного порушення (через `scanFileForBunSqlPatterns`).
|
|
51
|
+
- **Деталі:** проходить по всіх файлах послідовно (`for ... of`), щоб не створювати лавину паралельних `readFile`. Прапор `hasBunSqlImport` встановлюється тільки один раз — після першого знайденого `import { sql|SQL } from 'bun'`. У повернутому обʼєкті лічильник `unsafeTemplateInterp` присутній фактично, хоча у JSDoc-типі формально не задекларований (важлива деталь для подальшого використання у `check`).
|
|
52
|
+
|
|
53
|
+
### `collectPgUsageForFile(content, rel, pgUsage)`
|
|
54
|
+
|
|
55
|
+
- **Сигнатура:** `function collectPgUsageForFile(content: string, rel: string, pgUsage: Array<{ rel, imports, listenNotify }>): void`
|
|
56
|
+
- **Параметри:**
|
|
57
|
+
- `content` — повний вміст файлу.
|
|
58
|
+
- `rel` — posix-шлях відносно кореня репо.
|
|
59
|
+
- `pgUsage` — масив-акумулятор (мутується in place).
|
|
60
|
+
- **Повертає:** нічого.
|
|
61
|
+
- **Side effects:** мутує `pgUsage` через `push`.
|
|
62
|
+
- **Деталі:**
|
|
63
|
+
1. Дешевий текстовий pre-filter: `mayHaveListenNotify = LISTEN_NOTIFY_KEYWORD_RE.test(content) || NOTIFICATION_LITERAL_RE.test(content)`.
|
|
64
|
+
2. Якщо файл не імпортує `'pg'` І не пройшов pre-filter — швидкий `return` (AST не парситься).
|
|
65
|
+
3. Інакше викликаються AST-сканери `findPgLibImportInText` і `findPgListenNotifyUsageInText`.
|
|
66
|
+
4. Якщо обидва пусті — запис не додається.
|
|
67
|
+
5. Інакше у `pgUsage` пушиться `{ rel, imports, listenNotify }`.
|
|
68
|
+
|
|
69
|
+
Логіка економить памʼять (не зберігаються метадані файлів без сигналу) і CPU (AST не парситься для файлів без жодного зі слів LISTEN / NOTIFY / UNLISTEN / `'notification'` і без імпорту `'pg'`).
|
|
70
|
+
|
|
71
|
+
### `scanFileForBunSqlPatterns(content, rel, fail, counts)`
|
|
72
|
+
|
|
73
|
+
- **Сигнатура:** `function scanFileForBunSqlPatterns(content: string, rel: string, fail: (msg: string) => void, counts: { perRequest, unsafeCall, unsafeTemplateInterp, dynamicList, inListGuard, pgLeftover, pgFormatShim, queryWrapper }): void`
|
|
74
|
+
- **Параметри:**
|
|
75
|
+
- `content` — вміст файлу.
|
|
76
|
+
- `rel` — posix-шлях відносно `repoRoot`.
|
|
77
|
+
- `fail` — колбек з reporter'а для запису повідомлень про порушення.
|
|
78
|
+
- `counts` — обʼєкт-акумулятор лічильників (мутується).
|
|
79
|
+
- **Повертає:** нічого.
|
|
80
|
+
- **Side effects:** інкрементує лічильники `counts.*` та викликає `fail(...)` для кожного знайденого порушення.
|
|
81
|
+
- **Деталі:** запускає по черзі сімейство сканерів з `bun-sql-scan.mjs`:
|
|
82
|
+
|
|
83
|
+
| Сканер | Лічильник | Тип порушення |
|
|
84
|
+
| ------------------------------------------------ | ---------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
85
|
+
| `findBunSqlPerRequestConnectionInText` | `perRequest` | `new SQL(...)` всередині функції — має бути модульний singleton. |
|
|
86
|
+
| `findBunSqlUnsafeUseWithoutAllowMarkerInText` | `unsafeCall` | `<obj>.unsafe(...)` без маркера `// allow-unsafe: <reason>` на тому ж/попередньому рядку. |
|
|
87
|
+
| `findBunSqlUnsafeWithInterpolatedTemplateInText` | `unsafeTemplateInterp` | `sql.unsafe(\`...\${x}...\`)` з template-літералом + інтерполяцією (заборонено навіть з allow-маркером). |
|
|
88
|
+
| `findBunSqlPgLeftoverCallInText` | `pgLeftover` | `<obj>.connect(...)` / `<obj>.end(...)` у файлах з Bun SQL (Bun сам керує пулом). |
|
|
89
|
+
| `findUnsafeBunSqlDynamicSqlListInText` | `dynamicList` | Динамічний список через `.join(',')` у `IN (...)` / `VALUES (...)`. |
|
|
90
|
+
| `findUnsafeBunSqlInListMissingEmptyGuardInText` | `inListGuard` | IN-список без перевірки на пустоту з `throw` — повідомлення формує `messageForBunSqlInListGuard`. |
|
|
91
|
+
| `findPgFormatShimDefinitionInText` | `pgFormatShim` | Локальне визначення pg-format-сумісного шиму (`format` з `%L` / `%I` / `%s`, або `quoteLiteral` / `quoteIdent` тощо). |
|
|
92
|
+
| `findPgFormatLikeQueryWrapperInText` | `queryWrapper` | `query(text, params)` обгортка над `<obj>.unsafe(...)` — прихований pg-сумісний шим. |
|
|
93
|
+
|
|
94
|
+
Кожне повідомлення містить шлях, номер рядка, людський опис проблеми, посилання на правило `js-bun-db.mdc` і сам snippet порушення. Для `findPgFormatShimDefinitionInText` повідомлення розгалужується за `v.kind === 'format_function'` (повна функція з форматерами) vs інше (escape-хелпер на кшталт `quoteLiteral`).
|
|
95
|
+
|
|
96
|
+
### `checkPgDependencyAndUsage(pkgJsonPaths, repoRoot, pgUsage, reporter)`
|
|
97
|
+
|
|
98
|
+
- **Сигнатура:** `async function checkPgDependencyAndUsage(pkgJsonPaths: string[], repoRoot: string, pgUsage: Array<{ rel, imports, listenNotify }>, reporter: { fail: (m: string) => void }): Promise<{ pgDepFails: number, pgImportFails: number, pgDepsFound: number, hasAnyListenNotify: boolean, listenNotifyEvidence: string | null }>`
|
|
99
|
+
- **Параметри:**
|
|
100
|
+
- `pkgJsonPaths` — абсолютні шляхи всіх знайдених `package.json`.
|
|
101
|
+
- `repoRoot` — корінь репозиторію.
|
|
102
|
+
- `pgUsage` — метадані з `scanSourcesForBunSqlPatterns`.
|
|
103
|
+
- `reporter` — обʼєкт з `fail`.
|
|
104
|
+
- **Повертає:** обʼєкт зі статистикою:
|
|
105
|
+
- `pgDepFails` — скільки `package.json` мають `dependencies.pg` без підтвердженого LISTEN/NOTIFY у проекті.
|
|
106
|
+
- `pgImportFails` — скільки окремих файлів-імпортів `'pg'` не мають власного LISTEN/NOTIFY.
|
|
107
|
+
- `pgDepsFound` — скільки `package.json` оголошують `dependencies.pg` (для `pass`-повідомлення про виключення).
|
|
108
|
+
- `hasAnyListenNotify` — чи хоч десь у проекті є LISTEN/NOTIFY.
|
|
109
|
+
- `listenNotifyEvidence` — рядок виду `"<rel>:<line>"` — перший доказ LISTEN/NOTIFY (або `null`).
|
|
110
|
+
- **Side effects:** читає `package.json` через `readFile`; викликає `reporter.fail(...)` за кожне порушення.
|
|
111
|
+
- **Деталі:**
|
|
112
|
+
1. Спочатку шукає у `pgUsage` перший файл з непустим `listenNotify` (`firstWithListenNotify`) — фіксується доказ (`<rel>:<firstLine>`).
|
|
113
|
+
2. Цикл по `pkgJsonPaths`: парсить кожен `package.json` (`JSON.parse`), на невалідному — `continue` (це проблема інших правил). Якщо немає `dependencies.pg` — пропускає. Інакше інкрементує `pgDepsFound` і, якщо у проекті немає LISTEN/NOTIFY взагалі, інкрементує `pgDepFails` та пише `fail` про заборону `dependencies.pg`.
|
|
114
|
+
3. Цикл по `pgUsage`: для кожного файлу з імпортом `'pg'`, але без LISTEN/NOTIFY — інкрементує `pgImportFails` і пише `fail` по кожному імпорту окремо.
|
|
115
|
+
|
|
116
|
+
### `messageForBunSqlInListGuard(rel, v)`
|
|
117
|
+
|
|
118
|
+
- **Сигнатура:** `function messageForBunSqlInListGuard(rel: string, v: { line: number, snippet: string, name?: string, reason: string }): string`
|
|
119
|
+
- **Параметри:**
|
|
120
|
+
- `rel` — posix-шлях файлу.
|
|
121
|
+
- `v` — обʼєкт порушення від `findUnsafeBunSqlInListMissingEmptyGuardInText`: містить `line`, `snippet`, опційне `name` (ідентифікатор змінної) і `reason` (підвид діагностики).
|
|
122
|
+
- **Повертає:** готовий рядок-повідомлення для `fail`.
|
|
123
|
+
- **Side effects:** немає; чиста функція.
|
|
124
|
+
- **Деталі:** діагностика розгалужується за `v.reason`:
|
|
125
|
+
- `'missing_guard'` — змінна-IN не має перевірки на пустоту з `throw`.
|
|
126
|
+
- `'sql_helper_not_var'` — у `${sql(...)}` всередині IN-списку має бути Identifier (а не вираз/виклик).
|
|
127
|
+
- інакше (default) — значення IN-списку у template literal треба винести в окрему змінну, валідувати на пустоту і кинути `throw` замість прямої підстановки.
|
|
128
|
+
|
|
129
|
+
### `check(cwd?)`
|
|
130
|
+
|
|
131
|
+
- **Сигнатура:** `export async function check(cwd: string = process.cwd()): Promise<number>`
|
|
132
|
+
- **Параметри:**
|
|
133
|
+
- `cwd` — корінь репозиторію; за замовчуванням `process.cwd()`.
|
|
134
|
+
- **Повертає:** `0`, якщо порушень не знайдено; `1` — інакше. Точне значення обчислює `reporter.getExitCode()`.
|
|
135
|
+
- **Side effects:** читання файлів (`existsSync`, `readFile`, `walkDir`); виклики `reporter.pass(...)` / `reporter.fail(...)` (у дефолтній імплементації — друкують у консоль).
|
|
136
|
+
- **Деталі (порядок виконання):**
|
|
137
|
+
1. Створює reporter через `createCheckReporter()`; деструктурує `{ pass }`.
|
|
138
|
+
2. Перевіряє існування `package.json` у корені. Якщо немає — `pass('js-bun-db: package.json у корені відсутній — перевірку пропущено')` і ранній вихід.
|
|
139
|
+
3. Підвантажує `ignorePaths` через `loadCursorIgnorePaths(repoRoot)` (читає `.cursorignore` тощо).
|
|
140
|
+
4. Шукає всі `package.json` у репо (`findAllPackageJsonPaths`). Якщо нічого — `pass(...)` і вихід.
|
|
141
|
+
5. Збирає всі JS/TS-джерела для скану (`findAllSourcePathsForBunSqlScan`). Якщо нічого — `pass(...)` і вихід.
|
|
142
|
+
6. Сканує всі джерела (`scanSourcesForBunSqlPatterns`) і отримує лічильники + `pgUsage` + `hasBunSqlImport`.
|
|
143
|
+
7. Перевіряє dependency `pg` і per-file імпорти (`checkPgDependencyAndUsage`). На основі `pgDepFails === 0` / `pgImportFails === 0` пише відповідні `pass`-повідомлення (з різним текстом залежно від `pgDepsFound`).
|
|
144
|
+
8. Якщо `hasBunSqlImport === false` — `pass('Bun SQL не використовується в коді')` і ранній вихід (немає сенсу скаржитися на патерни Bun SQL у проекті, що ним не користується).
|
|
145
|
+
9. Інакше для кожної категорії з `count === 0` пише позитивний `pass` (`new SQL` singleton, `sql.unsafe` маркерована, `unsafeTemplateInterp`, pg-leftover, dynamic list, in-list guard, pg-format шими, query-обгортки).
|
|
146
|
+
10. Повертає `reporter.getExitCode()`.
|
|
147
|
+
|
|
148
|
+
## Залежності
|
|
149
|
+
|
|
150
|
+
Зовнішні (Node.js core):
|
|
151
|
+
|
|
152
|
+
- `node:fs` → `existsSync` — перевірка наявності кореневого `package.json`.
|
|
153
|
+
- `node:fs/promises` → `readFile` — асинхронне читання файлів.
|
|
154
|
+
- `node:path` → `join`, `relative` — побудова шляхів і обчислення відносних.
|
|
155
|
+
|
|
156
|
+
Внутрішні модулі репозиторію:
|
|
157
|
+
|
|
158
|
+
- `../../../scripts/lib/check-reporter.mjs` → `createCheckReporter` — фабрика reporter'а з `pass` / `fail` / `getExitCode`.
|
|
159
|
+
- `../lib/bun-sql-scan.mjs` — набір AST-сканерів і дешевих текстових пре-фільтрів:
|
|
160
|
+
- `findBunSqlPerRequestConnectionInText`
|
|
161
|
+
- `findBunSqlPgLeftoverCallInText`
|
|
162
|
+
- `findBunSqlUnsafeUseWithoutAllowMarkerInText`
|
|
163
|
+
- `findBunSqlUnsafeWithInterpolatedTemplateInText`
|
|
164
|
+
- `findPgFormatLikeQueryWrapperInText`
|
|
165
|
+
- `findPgFormatShimDefinitionInText`
|
|
166
|
+
- `findPgLibImportInText`
|
|
167
|
+
- `findPgListenNotifyUsageInText`
|
|
168
|
+
- `findUnsafeBunSqlDynamicSqlListInText`
|
|
169
|
+
- `findUnsafeBunSqlInListMissingEmptyGuardInText`
|
|
170
|
+
- `isBunSqlScanSourceFile` — фільтр шляхів (які файли скануються взагалі).
|
|
171
|
+
- `textHasBunSqlImport` — швидкий текстовий тест на наявність `import { sql|SQL } from 'bun'`.
|
|
172
|
+
- `textHasPgLibImport` — швидкий текстовий тест на наявність `import ... from 'pg'`.
|
|
173
|
+
- `../../../scripts/utils/find-package-json-paths.mjs` → `findAllPackageJsonPaths` — обхід репо і збір усіх `package.json` (з урахуванням ignore-паттернів).
|
|
174
|
+
- `../../../scripts/lib/load-cursor-config.mjs` → `loadCursorIgnorePaths` — список абсолютних шляхів, виключених з обходу (на основі конфігу Cursor).
|
|
175
|
+
- `../../../scripts/utils/walkDir.mjs` → `walkDir` — рекурсивний обхід каталогу з підтримкою ignore-list.
|
|
176
|
+
|
|
177
|
+
Покладається на наявність ESM, `bun`/`node` runtime з підтримкою top-level `process.cwd()`.
|
|
178
|
+
|
|
179
|
+
## Потік виконання / Використання
|
|
180
|
+
|
|
181
|
+
### Імпорт як бібліотека
|
|
182
|
+
|
|
183
|
+
```js
|
|
184
|
+
import { check } from './safety.mjs'
|
|
185
|
+
|
|
186
|
+
const exitCode = await check(process.cwd())
|
|
187
|
+
process.exit(exitCode)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Запуск у складі checker-пайплайна
|
|
191
|
+
|
|
192
|
+
Функція `check(cwd)` — це стандартний контракт правил `npm/rules/<rule-id>/js/safety.mjs` у репозиторії. Зовнішній runner викликає її, отримує `0` / `1` і за потреби агрегує з іншими правилами.
|
|
193
|
+
|
|
194
|
+
### Логічний потік `check`
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
check(cwd)
|
|
198
|
+
├── existsSync(<cwd>/package.json)? // ні → pass + exit
|
|
199
|
+
├── loadCursorIgnorePaths(repoRoot)
|
|
200
|
+
├── findAllPackageJsonPaths(...) // 0 → pass + exit
|
|
201
|
+
├── findAllSourcePathsForBunSqlScan(...) // 0 → pass + exit
|
|
202
|
+
├── scanSourcesForBunSqlPatterns(...)
|
|
203
|
+
│ для кожного файлу:
|
|
204
|
+
│ ├── textHasBunSqlImport → встановити hasBunSqlImport
|
|
205
|
+
│ ├── scanFileForBunSqlPatterns → 8 сканерів, fail + counts
|
|
206
|
+
│ └── collectPgUsageForFile → текст-prefilter → AST → push pgUsage
|
|
207
|
+
├── checkPgDependencyAndUsage(...)
|
|
208
|
+
│ ├── знайти firstWithListenNotify → listenNotifyEvidence
|
|
209
|
+
│ ├── по кожному package.json з dependencies.pg → fail якщо нема LISTEN/NOTIFY
|
|
210
|
+
│ └── по кожному файлу з import 'pg' без LISTEN/NOTIFY → fail
|
|
211
|
+
├── pass-повідомлення про pg (залежно від pgDepsFound / listenNotifyEvidence)
|
|
212
|
+
├── якщо !hasBunSqlImport → pass + exit
|
|
213
|
+
└── pass для кожної категорії з count === 0
|
|
214
|
+
(perRequest, unsafeCall, unsafeTemplateInterp,
|
|
215
|
+
pgLeftover, dynamicList, inListGuard,
|
|
216
|
+
pgFormatShim, queryWrapper)
|
|
217
|
+
└── return reporter.getExitCode()
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Як інтерпретувати вивід
|
|
221
|
+
|
|
222
|
+
- Кожне порушення друкується через `fail(...)` з префіксом `js-bun-db:`, шляхом `<rel>:<line>`, описом і snippet.
|
|
223
|
+
- Кожна чиста категорія дає `pass(...)` — це корисно для логів CI, щоб бачити, що перевірка не «мовчки» пропущена.
|
|
224
|
+
- Exit-код `0` гарантує, що жоден `fail` не був викликаний; `1` — є щонайменше один.
|
|
225
|
+
|
|
226
|
+
### Сценарії раннього виходу
|
|
227
|
+
|
|
228
|
+
- Корінь без `package.json` — нічого перевіряти.
|
|
229
|
+
- `package.json` не знайдено взагалі — те саме.
|
|
230
|
+
- Немає файлів для скану (`isBunSqlScanSourceFile` повернув `false` для всіх) — Bun SQL-патерни ніде шукати.
|
|
231
|
+
- У жодному файлі немає `import { sql|SQL } from 'bun'` — у проекті немає Bun SQL, тож перевірки на `new SQL`, `unsafe`, IN-списки тощо нерелевантні. Перевірка `pg`-dependency при цьому виконується завжди (до цього раннього виходу), бо її цінність не залежить від Bun SQL.
|
|
@@ -60,9 +60,9 @@ await pgWrite.unsafe(sql)
|
|
|
60
60
|
await pgWrite`
|
|
61
61
|
WITH s(id, date, data) AS (
|
|
62
62
|
SELECT * FROM unnest(
|
|
63
|
-
${pgWrite.array(batch
|
|
64
|
-
${pgWrite.array(batch
|
|
65
|
-
${pgWrite.array(batch
|
|
63
|
+
${pgWrite.array(col(batch, 'id'), 'int4')},
|
|
64
|
+
${pgWrite.array(col(batch, 'date'), 'date')},
|
|
65
|
+
${pgWrite.array(col(batch, 'data'), 'jsonb')}
|
|
66
66
|
)
|
|
67
67
|
)
|
|
68
68
|
MERGE INTO my_table AS t
|
|
@@ -107,7 +107,7 @@ await sql`SELECT * FROM unnest(${sql.array(batch.map(r => JSON.stringify(r.data)
|
|
|
107
107
|
// ✅ об'єкт/масив передається напряму
|
|
108
108
|
await sql`INSERT INTO events (details) VALUES (${detailsForEvent}::jsonb)`
|
|
109
109
|
|
|
110
|
-
await sql`SELECT * FROM unnest(${sql.array(batch
|
|
110
|
+
await sql`SELECT * FROM unnest(${sql.array(col(batch, 'data'), 'jsonb')})`
|
|
111
111
|
```
|
|
112
112
|
|
|
113
113
|
`UNION ALL`-цикл замість `unnest` підходить для малих динамічних запитів (2–5 рядків), де кожна гілка семантично різна. Для bulk upsert — завжди `unnest`.
|
|
@@ -300,18 +300,25 @@ const rows = await sql.unsafe(query, values)
|
|
|
300
300
|
|
|
301
301
|
Дефолтний експорт `sql` з `'bun'` сам читає змінні середовища (`DATABASE_URL`, `POSTGRES_URL`, `MYSQL_URL`, `PGHOST`/`PGUSER`/... та `MYSQL_HOST`/`MYSQL_USER`/...) і керує пулом — окремий `Pool` як у `pg` створювати не треба.
|
|
302
302
|
|
|
303
|
-
Для явного конфігу — `new SQL(...)` як **singleton** на рівні модуля, а не на кожен
|
|
303
|
+
Для явного конфігу — `new SQL(...)` як **singleton** на рівні модуля, а не на кожен запит. Файл кладеться у `src/conn/db.js` і експортує іменовані константи `pgWrite` (основний запис) та `pgRead` (read-only replica), щоб glob `**/src/conn/**` у правилах покривав ці файли:
|
|
304
304
|
|
|
305
305
|
```javascript
|
|
306
|
-
// db.js
|
|
306
|
+
// src/conn/db.js
|
|
307
307
|
import { SQL } from 'bun'
|
|
308
308
|
|
|
309
|
-
export const
|
|
309
|
+
export const pgWrite = new SQL({
|
|
310
310
|
url: process.env.DATABASE_URL,
|
|
311
311
|
max: 20,
|
|
312
312
|
idleTimeout: 30,
|
|
313
313
|
connectionTimeout: 10
|
|
314
314
|
})
|
|
315
|
+
|
|
316
|
+
export const pgRead = new SQL({
|
|
317
|
+
url: process.env.PG_CONN_READ ?? process.env.DATABASE_URL,
|
|
318
|
+
max: 10,
|
|
319
|
+
idleTimeout: 30,
|
|
320
|
+
connectionTimeout: 10
|
|
321
|
+
})
|
|
315
322
|
```
|
|
316
323
|
|
|
317
324
|
Connection string обирає адаптер автоматично:
|
|
@@ -443,6 +450,28 @@ ${pgRead.array(ids, 'int4')}
|
|
|
443
450
|
| date string | date | `'date'` |
|
|
444
451
|
| ISO datetime | timestamptz | `'timestamptz'` |
|
|
445
452
|
|
|
453
|
+
### `col(arr, key)` — хелпер для unnest-колонок
|
|
454
|
+
|
|
455
|
+
OXC formatter (oxfmt ≥ 0.49) примусово розгортає будь-який `CallExpression`, де перший аргумент є `CallExpression` з callback, у багаторядковий блок — незалежно від `printWidth`. Тому `pgWrite.array(arr.map(r => r.field), 'type')` всередині tagged template literal завжди стає 4-рядковим блоком. `col(arr, 'field')` (перший аргумент — identifier, другий — string literal) цей тригер не зачіпає і лишається однорядковим.
|
|
456
|
+
|
|
457
|
+
Канонічне місце хелпера — `src/utils/col.js` (або `src/conn/col.js` залежно від структури проєкту):
|
|
458
|
+
|
|
459
|
+
```javascript
|
|
460
|
+
// src/utils/col.js
|
|
461
|
+
export const col = (arr, key) => arr.map(r => r[key])
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
```javascript
|
|
465
|
+
import { pgWrite } from '#src/conn/db.js'
|
|
466
|
+
import { col } from '#src/utils/col.js'
|
|
467
|
+
|
|
468
|
+
// ❌ oxfmt розгортає на 4+ рядки незалежно від printWidth
|
|
469
|
+
${pgWrite.array(rows.map(r => r.id), 'int4')}
|
|
470
|
+
|
|
471
|
+
// ✅ col(arr, key) — перший аргумент не є callback; oxfmt лишає однорядковим
|
|
472
|
+
${pgWrite.array(col(rows, 'id'), 'int4')}
|
|
473
|
+
```
|
|
474
|
+
|
|
446
475
|
### Повний приклад (UNNEST + MERGE)
|
|
447
476
|
|
|
448
477
|
```javascript
|
|
@@ -450,10 +479,10 @@ await pgWrite`
|
|
|
450
479
|
MERGE INTO "order".product p
|
|
451
480
|
USING (
|
|
452
481
|
SELECT * FROM unnest(
|
|
453
|
-
${pgWrite.array(rows
|
|
454
|
-
${pgWrite.array(rows
|
|
455
|
-
${pgWrite.array(rows
|
|
456
|
-
${pgWrite.array(rows
|
|
482
|
+
${pgWrite.array(col(rows, 'order_id'), 'uuid')},
|
|
483
|
+
${pgWrite.array(col(rows, 'product_id'), 'int4')},
|
|
484
|
+
${pgWrite.array(col(rows, 'qty'), 'numeric')},
|
|
485
|
+
${pgWrite.array(col(rows, 'is_refund'), 'bool')}
|
|
457
486
|
) AS s(order_id, product_id, qty, is_refund)
|
|
458
487
|
) AS s ON p.order_id = s.order_id AND p.product_id = s.product_id
|
|
459
488
|
WHEN MATCHED THEN
|
|
@@ -582,8 +611,8 @@ ws.connect(url) // allow-pg-leftover: WebSocket, не pg
|
|
|
582
611
|
```javascript
|
|
583
612
|
// ❌ нове підключення/інстанс на кожен виклик
|
|
584
613
|
function getUser(id) {
|
|
585
|
-
const
|
|
586
|
-
return
|
|
614
|
+
const pgWrite = new SQL(process.env.DATABASE_URL)
|
|
615
|
+
return pgWrite`SELECT * FROM users WHERE id = ${id}`
|
|
587
616
|
}
|
|
588
617
|
```
|
|
589
618
|
|