@nitra/cursor 1.8.206 → 1.8.208
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 +36 -0
- package/mdc/js-run.mdc +49 -2
- package/package.json +1 -1
- package/policy/abie/health_check_policy/health_check_policy.rego +73 -0
- package/policy/abie/http_route_base/http_route_base.rego +45 -0
- package/policy/adr/settings_json/settings_json.rego +31 -0
- package/policy/adr/settings_local_json/settings_local_json.rego +28 -0
- package/policy/bun/bunfig/bunfig.rego +33 -0
- package/policy/bun/package_json/package_json.rego +94 -0
- package/policy/capacitor/package_json/package_json.rego +45 -0
- package/policy/ga/clean_ga_workflows/clean_ga_workflows.rego +0 -26
- package/policy/ga/clean_merged_branch/clean_merged_branch.rego +0 -25
- package/policy/ga/git_ai/git_ai.rego +0 -26
- package/policy/ga/lint_ga/lint_ga.rego +0 -26
- package/policy/ga/workflow_common/workflow_common.rego +161 -0
- package/policy/graphql/package_json/package_json.rego +35 -0
- package/policy/hasura/svc_hl/svc_hl.rego +27 -0
- package/policy/image_compress/package_json/package_json.rego +94 -0
- package/policy/js_bun_db/package_json/package_json.rego +28 -0
- package/policy/js_lint/lint_js_yml/lint_js_yml.rego +98 -0
- package/policy/js_lint/package_json/package_json.rego +137 -0
- package/policy/js_mssql/package_json/package_json.rego +57 -0
- package/policy/js_run/configmap/configmap.rego +45 -0
- package/policy/js_run/jsconfig/jsconfig.rego +66 -0
- package/policy/js_run/package_json/package_json.rego +31 -0
- package/policy/k8s/manifest/manifest.rego +130 -0
- package/policy/npm_module/emit_types_config/emit_types_config.rego +37 -0
- package/policy/npm_module/npm_package_json/npm_package_json.rego +55 -0
- package/policy/npm_module/npm_publish_yml/npm_publish_yml.rego +79 -0
- package/policy/npm_module/root_package_json/root_package_json.rego +28 -0
- package/policy/php/lint_php_yml/lint_php_yml.rego +32 -0
- package/policy/php/package_json/package_json.rego +19 -0
- package/policy/style_lint/lint_style_yml/lint_style_yml.rego +35 -0
- package/policy/style_lint/package_json/package_json.rego +49 -0
- package/policy/text/cspell/cspell.rego +91 -0
- package/policy/text/markdownlint/markdownlint.rego +21 -0
- package/policy/text/oxfmtrc/oxfmtrc.rego +90 -0
- package/policy/text/package_json/package_json.rego +88 -0
- package/policy/vue/package_json/package_json.rego +54 -0
- package/scripts/check-adr.mjs +3 -2
- package/scripts/check-bun.mjs +21 -117
- package/scripts/check-graphql.mjs +6 -45
- package/scripts/check-hasura.mjs +2 -3
- package/scripts/check-image-avif.mjs +3 -3
- package/scripts/check-image-compress.mjs +25 -132
- package/scripts/check-js-bun-db.mjs +3 -50
- package/scripts/check-js-run.mjs +84 -86
- package/scripts/check-k8s.mjs +4 -4
- package/scripts/check-npm-module.mjs +17 -8
- package/scripts/check-php.mjs +16 -51
- package/scripts/check-style-lint.mjs +28 -52
- package/scripts/check-text.mjs +47 -219
- package/scripts/check-vue.mjs +3 -16
- package/scripts/lint-conftest.mjs +351 -0
- package/scripts/lint-ga.mjs +39 -2
- package/scripts/run-shellcheck-text.mjs +2 -2
- package/scripts/utils/conn-file-rules.mjs +170 -0
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Перевіряє правило graphql.mdc: наявність **`.graphqlrc.yml
|
|
3
|
-
* **`graphql.vscode-graphql
|
|
4
|
-
* **`package.json`**, якщо у дереві є **`gql\`…\``**.
|
|
2
|
+
* Перевіряє правило graphql.mdc: наявність **`.graphqlrc.yml`** і рекомендації
|
|
3
|
+
* **`graphql.vscode-graphql`**, якщо у дереві є **`gql\`…\``**.
|
|
5
4
|
*
|
|
6
5
|
* Обхід репозиторію — **`walkDir`** від **`process.cwd()`** (пропуски як у інших check). Кандидати — **`.vue`** та **`.js`/`.ts`/`.jsx`/`.tsx`** тощо; пропуск **`.d.ts`**, **auto-imports.d.ts** тощо — **`shouldSkipFileForGqlScan`**.
|
|
7
6
|
*
|
|
8
7
|
* Виявлення **`gql`** — **oxc-parser** після витягування `<script>` з SFC (**`graphql-gql-scan.mjs`**). Якщо збігів немає — перевірка завершується успішно без вимог до конфігів.
|
|
8
|
+
*
|
|
9
|
+
* Перевірку `scripts.dump-schema == REQUIRED_DUMP_SCHEMA_SCRIPT` у `package.json`
|
|
10
|
+
* перенесено в Rego (`npm/policy/graphql/package_json/`); `bun run lint-conftest`
|
|
11
|
+
* запускає її окремо.
|
|
9
12
|
*/
|
|
10
13
|
import { existsSync } from 'node:fs'
|
|
11
14
|
import { readFile } from 'node:fs/promises'
|
|
@@ -25,9 +28,6 @@ export const GRAPHQL_RC_FILENAME = '.graphqlrc.yml'
|
|
|
25
28
|
|
|
26
29
|
/** Розширення VS Code з graphql.mdc. */
|
|
27
30
|
export const REQUIRED_GRAPHQL_VSCODE_EXTENSION = 'graphql.vscode-graphql'
|
|
28
|
-
/** Команда dump-schema з graphql.mdc. */
|
|
29
|
-
export const REQUIRED_DUMP_SCHEMA_SCRIPT =
|
|
30
|
-
"bunx graphqurl http://localhost:4040/v1/graphql -H 'X-Hasura-Admin-Secret: secret' --introspect > schema.graphql"
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Збирає абсолютні шляхи source-файлів, які підлягають скануванню на gql templates.
|
|
@@ -106,44 +106,6 @@ async function checkExtensionsRecommendation(pass, fail) {
|
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
/**
|
|
110
|
-
* Перевіряє `package.json` і значення scripts.dump-schema.
|
|
111
|
-
* @param {(msg: string) => void} pass success-репортер
|
|
112
|
-
* @param {(msg: string) => void} fail fail-репортер
|
|
113
|
-
* @returns {Promise<void>}
|
|
114
|
-
*/
|
|
115
|
-
async function checkPackageDumpSchemaScript(pass, fail) {
|
|
116
|
-
if (!existsSync('package.json')) {
|
|
117
|
-
fail('Відсутній package.json у корені репозиторію')
|
|
118
|
-
return
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
let pkg
|
|
122
|
-
try {
|
|
123
|
-
pkg = JSON.parse(await readFile('package.json', 'utf8'))
|
|
124
|
-
} catch {
|
|
125
|
-
fail('package.json не є валідним JSON')
|
|
126
|
-
return
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const scripts = pkg.scripts
|
|
130
|
-
if (!scripts || typeof scripts !== 'object' || Array.isArray(scripts)) {
|
|
131
|
-
fail('package.json: поле scripts має бути обʼєктом')
|
|
132
|
-
return
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (!Object.hasOwn(scripts, 'dump-schema')) {
|
|
136
|
-
fail('package.json: відсутній scripts.dump-schema (graphql.mdc)')
|
|
137
|
-
return
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (scripts['dump-schema'] === REQUIRED_DUMP_SCHEMA_SCRIPT) {
|
|
141
|
-
pass('package.json: scripts.dump-schema відповідає graphql.mdc')
|
|
142
|
-
} else {
|
|
143
|
-
fail(`package.json: scripts.dump-schema має бути "${REQUIRED_DUMP_SCHEMA_SCRIPT}" (graphql.mdc)`)
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
109
|
/**
|
|
148
110
|
* Перевіряє graphql.mdc: умовна вимога .graphqlrc.yml, graphql.vscode-graphql
|
|
149
111
|
* і scripts.dump-schema за наявності gql tagged templates.
|
|
@@ -176,7 +138,6 @@ export async function check() {
|
|
|
176
138
|
}
|
|
177
139
|
|
|
178
140
|
await checkExtensionsRecommendation(pass, fail)
|
|
179
|
-
await checkPackageDumpSchemaScript(pass, fail)
|
|
180
141
|
|
|
181
142
|
return reporter.getExitCode()
|
|
182
143
|
}
|
package/scripts/check-hasura.mjs
CHANGED
|
@@ -46,7 +46,6 @@ const HASURA_ENDPOINT_LINE_RE = /^[ \t]*(?:export[ \t]+)?HASURA_GRAPHQL_ENDPOINT
|
|
|
46
46
|
// Дозволяємо два DNS-суфікси кластера: `<name>.internal` (GKE/GCP) і `cluster.local`
|
|
47
47
|
// (стандартний k8s / Yandex Cloud). У YC namespace.yaml + cluster mode дають коротший суфікс.
|
|
48
48
|
const INTERNAL_HASURA_URL_RE = /^http:\/\/([^./]+)\.([^./]+)\.svc\.((?:[^./:]+\.internal)|cluster\.local):(\d+)\/?$/u
|
|
49
|
-
const CLUSTER_LOCAL_SUFFIX = 'cluster.local'
|
|
50
49
|
const INTERNAL_DNS_SUFFIX = '.internal'
|
|
51
50
|
|
|
52
51
|
/**
|
|
@@ -149,9 +148,9 @@ async function checkEnvFile(relPath, expected, reporter) {
|
|
|
149
148
|
const value = m[1].trim()
|
|
150
149
|
const parsed = parseInternalHasuraEndpoint(value)
|
|
151
150
|
if (!parsed.ok) {
|
|
152
|
-
|
|
151
|
+
|
|
153
152
|
const example =
|
|
154
|
-
|
|
153
|
+
"https://<service>.<namespace>.svc.<cluster>.internal:<port> або http://<service>.<namespace>.svc.cluster.local:<port>"
|
|
155
154
|
fail(
|
|
156
155
|
`${relPath}: HASURA_GRAPHQL_ENDPOINT="${value}" — потрібен внутрішній кластерний URL виду ${example} (hasura.mdc)`
|
|
157
156
|
)
|
|
@@ -68,6 +68,7 @@ const VUE_RASTER_STATIC_SRC_RE = /(?<![:\-_.])\bsrc\s*=\s*['"]([^'"\s]+\.(?:png|
|
|
|
68
68
|
* є сиротами і підлягають видаленню.
|
|
69
69
|
*/
|
|
70
70
|
const VUE_AVIF_REF_RE = /['"]([^'"\s]+\.(?:png|jpe?g|gif)\.avif)['"]/giu
|
|
71
|
+
const RASTER_IMAGE_EXT_RE = /\.(?:png|jpe?g|gif)$/iu
|
|
71
72
|
|
|
72
73
|
/**
|
|
73
74
|
* Чи у `package.json` пакета вимкнено avif-перевірку Vue-імпортів.
|
|
@@ -113,8 +114,7 @@ function resolveImageCandidates(importPath, sourceAbsPath, packageRootAbs) {
|
|
|
113
114
|
/** @type {string[]} */
|
|
114
115
|
const candidates = []
|
|
115
116
|
if (packageRootAbs) {
|
|
116
|
-
candidates.push(join(packageRootAbs, 'public', importPath))
|
|
117
|
-
candidates.push(join(packageRootAbs, importPath))
|
|
117
|
+
candidates.push(join(packageRootAbs, 'public', importPath), join(packageRootAbs, importPath))
|
|
118
118
|
}
|
|
119
119
|
candidates.push(join(process.cwd(), importPath))
|
|
120
120
|
return candidates
|
|
@@ -291,7 +291,7 @@ async function hasAnyRasterImage(ignorePaths) {
|
|
|
291
291
|
process.cwd(),
|
|
292
292
|
absPath => {
|
|
293
293
|
if (found) return
|
|
294
|
-
if (
|
|
294
|
+
if (RASTER_IMAGE_EXT_RE.test(absPath)) found = true
|
|
295
295
|
},
|
|
296
296
|
ignorePaths
|
|
297
297
|
)
|
|
@@ -1,121 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Перевіряє
|
|
3
|
-
*
|
|
2
|
+
* Перевіряє вимоги правила `image-compress.mdc` для оптимізації raster/SVG через
|
|
3
|
+
* `@nitra/minify-image` ≥ 3.2.0 (локально).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* - застарілий `.minify-image-cache.tsv` (з версій < 3.2) видалений з кореня — інакше
|
|
19
|
-
* проєкт лишається у напівпереміщеному стані.
|
|
5
|
+
* **Що тут лишилося** (FS / cross-file):
|
|
6
|
+
* - наявність `package.json` у корені;
|
|
7
|
+
* - `.n-minify-image.tsv` (committed source of truth з sha1/originalSize/size) НЕ
|
|
8
|
+
* в `.gitignore` — він має бути в git;
|
|
9
|
+
* - застарілий `.minify-image-cache.tsv` (з версій < 3.2) видалений з кореня та
|
|
10
|
+
* з `.gitignore`.
|
|
11
|
+
*
|
|
12
|
+
* **Що покрила Rego** (`bun run lint-conftest`,
|
|
13
|
+
* `npm/policy/image_compress/package_json/`):
|
|
14
|
+
* - `scripts.lint-image` викликає `npx @nitra/minify-image --src=. --write`
|
|
15
|
+
* без `--avif` (AVIF — окреме правило `image-avif`);
|
|
16
|
+
* - агрегований `lint` (якщо є) містить `bun run lint-image`;
|
|
17
|
+
* - `@nitra/minify-image` НЕ у `dependencies` / `devDependencies` (через `npx`).
|
|
20
18
|
*/
|
|
21
19
|
import { existsSync } from 'node:fs'
|
|
22
20
|
import { readFile } from 'node:fs/promises'
|
|
23
21
|
|
|
24
22
|
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
25
23
|
|
|
26
|
-
/** Імʼя CLI-пакета: рядок у `lint-image` і заборонений у залежностях. */
|
|
27
|
-
const MINIFY_PACKAGE_NAME = '@nitra/minify-image'
|
|
28
|
-
|
|
29
24
|
/** Імʼя committed-кешу (sha1 + originalSize + size) у `@nitra/minify-image` ≥ 3.2.0. */
|
|
30
25
|
const HASH_CACHE_FILENAME = '.n-minify-image.tsv'
|
|
31
26
|
|
|
32
27
|
/** Імʼя застарілого 4-колонкового кешу (`@nitra/minify-image` < 3.2). Має бути видалений після міграції. */
|
|
33
28
|
const LEGACY_CACHE_FILENAME = '.minify-image-cache.tsv'
|
|
34
29
|
|
|
35
|
-
/**
|
|
36
|
-
* Перевіряє скрипт `lint-image` у `package.json`.
|
|
37
|
-
*
|
|
38
|
-
* Має містити виклик `npx @nitra/minify-image` з обовʼязковими прапорцями `--src=.`
|
|
39
|
-
* і `--write` (авто-оптимізація на місці). Прапорець `--avif` у `lint-image`
|
|
40
|
-
* заборонений — AVIF-генерацію виконує `check image-avif`, інакше `bun run lint` плодить
|
|
41
|
-
* `.avif` для зображень, що ніде не вживаються.
|
|
42
|
-
* @param {string|undefined} lintImage значення `scripts['lint-image']`
|
|
43
|
-
* @param {(msg: string) => void} pass callback при успішній перевірці
|
|
44
|
-
* @param {(msg: string) => void} fail callback при помилці
|
|
45
|
-
* @returns {void}
|
|
46
|
-
*/
|
|
47
|
-
function checkLintImageScript(lintImage, pass, fail) {
|
|
48
|
-
const canonical = `npx ${MINIFY_PACKAGE_NAME} --src=. --write`
|
|
49
|
-
if (typeof lintImage !== 'string' || !lintImage.trim()) {
|
|
50
|
-
fail(`package.json: додай скрипт "lint-image" з \`${canonical}\` (image-compress.mdc)`)
|
|
51
|
-
return
|
|
52
|
-
}
|
|
53
|
-
if (!lintImage.includes(`npx ${MINIFY_PACKAGE_NAME}`)) {
|
|
54
|
-
fail(`package.json: lint-image має викликати \`npx ${MINIFY_PACKAGE_NAME}\` (image-compress.mdc)`)
|
|
55
|
-
return
|
|
56
|
-
}
|
|
57
|
-
/** @type {{ flag: string, variants: string[], hint: string }[]} */
|
|
58
|
-
const requiredFlags = [
|
|
59
|
-
{ flag: '--src=.', variants: ['--src=.', '--src .'], hint: '`--src=.`' },
|
|
60
|
-
{ flag: '--write', variants: ['--write'], hint: '`--write` (авто-оптимізація на місці)' }
|
|
61
|
-
]
|
|
62
|
-
const missing = requiredFlags.filter(f => !f.variants.some(v => lintImage.includes(v)))
|
|
63
|
-
if (missing.length > 0) {
|
|
64
|
-
fail(
|
|
65
|
-
`package.json: lint-image має містити ${missing.map(f => f.hint).join(', ')} — канонічний виклик: \`${canonical}\` (image-compress.mdc)`
|
|
66
|
-
)
|
|
67
|
-
return
|
|
68
|
-
}
|
|
69
|
-
if (lintImage.includes('--avif')) {
|
|
70
|
-
fail(
|
|
71
|
-
`package.json: прибери \`--avif\` з lint-image — AVIF-генерацію виконує \`npx @nitra/cursor check image-avif\` (image-compress.mdc). Канонічний виклик: \`${canonical}\``
|
|
72
|
-
)
|
|
73
|
-
return
|
|
74
|
-
}
|
|
75
|
-
pass(`package.json: lint-image викликає \`${canonical}\``)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Перевіряє, що агрегований `lint` (якщо є) кличе `bun run lint-image` —
|
|
80
|
-
* симетрично до `lint-text`, `lint-js`, `lint-ga`.
|
|
81
|
-
* @param {string|undefined} lintAggregate значення `scripts.lint`
|
|
82
|
-
* @param {(msg: string) => void} pass callback при успішній перевірці
|
|
83
|
-
* @param {(msg: string) => void} fail callback при помилці
|
|
84
|
-
* @returns {void}
|
|
85
|
-
*/
|
|
86
|
-
function checkLintAggregateIncludesImage(lintAggregate, pass, fail) {
|
|
87
|
-
if (typeof lintAggregate !== 'string' || !lintAggregate.trim()) {
|
|
88
|
-
return
|
|
89
|
-
}
|
|
90
|
-
if (lintAggregate.includes('bun run lint-image')) {
|
|
91
|
-
pass('package.json: агрегований `lint` викликає `bun run lint-image`')
|
|
92
|
-
} else {
|
|
93
|
-
fail(
|
|
94
|
-
'package.json: у `lint` додай `bun run lint-image` (image-compress.mdc, симетрично до lint-text / lint-js / lint-ga)'
|
|
95
|
-
)
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Забороняє `@nitra/minify-image` у `dependencies` чи `devDependencies` —
|
|
101
|
-
* CLI завжди запускається через `npx` (як `markdownlint-cli2` у `text.mdc`).
|
|
102
|
-
* @param {{ dependencies?: Record<string, unknown>, devDependencies?: Record<string, unknown> }} pkg розібраний package.json
|
|
103
|
-
* @param {(msg: string) => void} pass callback при успішній перевірці
|
|
104
|
-
* @param {(msg: string) => void} fail callback при помилці
|
|
105
|
-
* @returns {void}
|
|
106
|
-
*/
|
|
107
|
-
function checkMinifyImageNotInDeps(pkg, pass, fail) {
|
|
108
|
-
const inDeps = Boolean(pkg.dependencies && MINIFY_PACKAGE_NAME in pkg.dependencies)
|
|
109
|
-
const inDevDeps = Boolean(pkg.devDependencies && MINIFY_PACKAGE_NAME in pkg.devDependencies)
|
|
110
|
-
if (inDeps || inDevDeps) {
|
|
111
|
-
fail(
|
|
112
|
-
`package.json: ${MINIFY_PACKAGE_NAME} не додавай у dependencies/devDependencies — лише через \`npx\` (image-compress.mdc)`
|
|
113
|
-
)
|
|
114
|
-
} else {
|
|
115
|
-
pass(`package.json: ${MINIFY_PACKAGE_NAME} не оголошено в dependencies/devDependencies`)
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
30
|
/**
|
|
120
31
|
* Зчитує всі змістовні рядки `.gitignore` (без коментарів і порожніх). Якщо файла нема — `null`.
|
|
121
32
|
* @returns {Promise<string[] | null>} список trim-нутих рядків або `null`
|
|
@@ -176,41 +87,23 @@ async function checkLegacyCacheRemoved(pass, fail) {
|
|
|
176
87
|
}
|
|
177
88
|
|
|
178
89
|
/**
|
|
179
|
-
* Перевіряє
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
* @returns {Promise<boolean>} `true`, якщо `package.json` знайдено й оброблено; `false` — нема
|
|
183
|
-
*/
|
|
184
|
-
async function checkPackageJsonImage(pass, fail) {
|
|
185
|
-
if (!existsSync('package.json')) {
|
|
186
|
-
fail('package.json не знайдено в корені — додай (image-compress.mdc)')
|
|
187
|
-
return false
|
|
188
|
-
}
|
|
189
|
-
const pkg = JSON.parse(await readFile('package.json', 'utf8'))
|
|
190
|
-
const scripts = /** @type {Record<string, unknown>} */ (pkg.scripts || {})
|
|
191
|
-
checkLintImageScript(typeof scripts['lint-image'] === 'string' ? scripts['lint-image'] : undefined, pass, fail)
|
|
192
|
-
checkLintAggregateIncludesImage(typeof scripts.lint === 'string' ? scripts.lint : undefined, pass, fail)
|
|
193
|
-
checkMinifyImageNotInDeps(pkg, pass, fail)
|
|
194
|
-
return true
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Перевіряє відповідність проєкту правилу `image-compress.mdc`: канонічний `lint-image`
|
|
199
|
-
* (через `npx @nitra/minify-image --src=. --write`, без `--avif`!), агрегований `lint`,
|
|
200
|
-
* `@nitra/minify-image` не у залежностях, `.n-minify-image.tsv` НЕ в `.gitignore`,
|
|
201
|
-
* застарілий `.minify-image-cache.tsv` видалений. CI-workflow для image не вимагається —
|
|
202
|
-
* лінт зображень виконується лише локально.
|
|
90
|
+
* Перевіряє відповідність проєкту правилу `image-compress.mdc`: `.n-minify-image.tsv` НЕ
|
|
91
|
+
* в `.gitignore`, застарілий `.minify-image-cache.tsv` видалений. CI-workflow для image
|
|
92
|
+
* не вимагається — лінт зображень виконується лише локально.
|
|
203
93
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
204
94
|
*/
|
|
205
95
|
export async function check() {
|
|
206
96
|
const reporter = createCheckReporter()
|
|
207
97
|
const { pass, fail } = reporter
|
|
208
98
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
await checkLegacyCacheRemoved(pass, fail)
|
|
99
|
+
if (!existsSync('package.json')) {
|
|
100
|
+
fail('package.json не знайдено в корені — додай (image-compress.mdc)')
|
|
101
|
+
return reporter.getExitCode()
|
|
213
102
|
}
|
|
103
|
+
pass('package.json є (структуру перевіряє bun run lint-conftest → image_compress.package_json)')
|
|
104
|
+
|
|
105
|
+
await checkHashCacheNotIgnored(pass, fail)
|
|
106
|
+
await checkLegacyCacheRemoved(pass, fail)
|
|
214
107
|
|
|
215
108
|
return reporter.getExitCode()
|
|
216
109
|
}
|
|
@@ -39,18 +39,6 @@ import { findAllPackageJsonPaths } from './utils/find-package-json-paths.mjs'
|
|
|
39
39
|
import { loadCursorIgnorePaths } from './utils/load-cursor-config.mjs'
|
|
40
40
|
import { walkDir } from './utils/walkDir.mjs'
|
|
41
41
|
|
|
42
|
-
/** Імена забороненої залежності у будь-якому `package.json`. */
|
|
43
|
-
const FORBIDDEN_DEPENDENCIES = Object.freeze(['pg', 'pg-format', 'mysql2'])
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* @param {unknown} v parsed JSON
|
|
47
|
-
* @returns {Record<string, unknown>} object або {}
|
|
48
|
-
*/
|
|
49
|
-
function asObject(v) {
|
|
50
|
-
if (!v || typeof v !== 'object' || Array.isArray(v)) return {}
|
|
51
|
-
return /** @type {Record<string, unknown>} */ (v)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
42
|
/**
|
|
55
43
|
* Збирає абсолютні шляхи JS/TS джерел у репозиторії для скану Bun SQL патернів.
|
|
56
44
|
* @param {string} repoRoot абсолютний шлях до кореня репозиторію
|
|
@@ -74,43 +62,6 @@ async function findAllSourcePathsForBunSqlScan(repoRoot, ignorePaths) {
|
|
|
74
62
|
return paths
|
|
75
63
|
}
|
|
76
64
|
|
|
77
|
-
/**
|
|
78
|
-
* Перевіряє, чи в кореневому `package.json` присутні заборонені пакети у `dependencies`.
|
|
79
|
-
* @param {string[]} pkgJsonPaths абсолютні шляхи всіх `package.json` у репо
|
|
80
|
-
* @param {string} repoRoot абсолютний шлях до кореня
|
|
81
|
-
* @param {{ pass: (m: string) => void, fail: (m: string) => void }} reporter колбеки pass і fail з перевірки
|
|
82
|
-
* @returns {Promise<number>} кількість знайдених порушень
|
|
83
|
-
*/
|
|
84
|
-
async function checkForbiddenDependencies(pkgJsonPaths, repoRoot, reporter) {
|
|
85
|
-
const { pass, fail } = reporter
|
|
86
|
-
let bad = 0
|
|
87
|
-
for (const absPath of pkgJsonPaths) {
|
|
88
|
-
const rel = relative(repoRoot, absPath).split('\\').join('/')
|
|
89
|
-
let parsed
|
|
90
|
-
try {
|
|
91
|
-
parsed = JSON.parse(await readFile(absPath, 'utf8'))
|
|
92
|
-
} catch {
|
|
93
|
-
fail(`js-bun-db: ${rel} — невалідний JSON`)
|
|
94
|
-
bad++
|
|
95
|
-
continue
|
|
96
|
-
}
|
|
97
|
-
const deps = asObject(parsed.dependencies)
|
|
98
|
-
for (const name of FORBIDDEN_DEPENDENCIES) {
|
|
99
|
-
if (Object.hasOwn(deps, name)) {
|
|
100
|
-
bad++
|
|
101
|
-
fail(
|
|
102
|
-
`js-bun-db: ${rel}: dependencies.${name} — замінити на Bun native SQL ` +
|
|
103
|
-
`(import { sql, SQL } from 'bun', https://bun.com/docs/runtime/sql) (js-bun-db.mdc)`
|
|
104
|
-
)
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
if (bad === 0) {
|
|
109
|
-
pass(`js-bun-db: жоден package.json не містить ${FORBIDDEN_DEPENDENCIES.join(' / ')} у dependencies`)
|
|
110
|
-
}
|
|
111
|
-
return bad
|
|
112
|
-
}
|
|
113
|
-
|
|
114
65
|
/**
|
|
115
66
|
* Сканує JS/TS-джерела на небезпечні патерни Bun SQL.
|
|
116
67
|
* @param {string[]} sourcePaths абсолютні шляхи джерел
|
|
@@ -233,7 +184,9 @@ export async function check() {
|
|
|
233
184
|
return reporter.getExitCode()
|
|
234
185
|
}
|
|
235
186
|
|
|
236
|
-
|
|
187
|
+
// Перевірку `dependencies` (заборона `pg` / `pg-format` / `mysql2`) перенесено
|
|
188
|
+
// в Rego-полісі `npm/policy/js_bun_db/package_json/`; `bun run lint-conftest`
|
|
189
|
+
// запускає її по всіх workspace-`package.json`. Тут лишився лише AST-скан коду.
|
|
237
190
|
|
|
238
191
|
const sourcePaths = await findAllSourcePathsForBunSqlScan(repoRoot, ignorePaths)
|
|
239
192
|
if (sourcePaths.length === 0) {
|
package/scripts/check-js-run.mjs
CHANGED
|
@@ -12,6 +12,11 @@
|
|
|
12
12
|
* дозволені лише у каталозі conn (за замовчуванням `src/conn/`; за наявності
|
|
13
13
|
* `package.json#imports['#conn/*']` — у його цільовому каталозі); поза ним — порушення
|
|
14
14
|
* (див. `utils/conn-imports-scan.mjs`);
|
|
15
|
+
* - «Нейминг та експорти у `#conn/`»: всередині conn-каталогу basename файла має відповідати
|
|
16
|
+
* канону `ql-<id>` / `(pg|mysql)-(read|write)[-<id>]`; `export default` заборонений; має бути
|
|
17
|
+
* іменований експорт з імʼям, що дорівнює camelCase від basename файла (`pg-write-contract.js`
|
|
18
|
+
* → `export const pgWriteContract`); `index.*` як reexport-барель пропускаємо
|
|
19
|
+
* (див. `utils/conn-file-rules.mjs`);
|
|
15
20
|
* - «process.env / CheckEnv»: пряме `process.env.X` має бути замінено на `env` —
|
|
16
21
|
* з `@nitra/check-env` (для обов'язкових змінних, із `checkEnv([...])`) або з
|
|
17
22
|
* `node:process` (для опційних). Коли `env` імпортовано з `@nitra/check-env`,
|
|
@@ -40,6 +45,7 @@ import {
|
|
|
40
45
|
import { findUncheckedProcessEnvInText, isCheckEnvScanSourceFile } from './utils/check-env-scan.mjs'
|
|
41
46
|
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
42
47
|
import { findDepcheckViolationsForPackage, readAllWorkflowFiles } from './utils/depcheck-workflow.mjs'
|
|
48
|
+
import { findConnFileRuleViolations, isConnFileRulesSourceFile } from './utils/conn-file-rules.mjs'
|
|
43
49
|
import {
|
|
44
50
|
findConnFactoryImportsInText,
|
|
45
51
|
isConnImportsScanSourceFile,
|
|
@@ -51,52 +57,10 @@ import { findPromiseSetTimeoutInText, isPromiseSetTimeoutScanSourceFile } from '
|
|
|
51
57
|
import { walkDir } from './utils/walkDir.mjs'
|
|
52
58
|
import { getMonorepoPackageRootDirs } from './utils/workspaces.mjs'
|
|
53
59
|
|
|
54
|
-
/** Канонічний `jsconfig.json` для backend workspace-пакетів із каталогом `src/` (js-run.mdc). */
|
|
55
|
-
const CANONICAL_BACKEND_JSCONFIG = Object.freeze({
|
|
56
|
-
compilerOptions: Object.freeze({
|
|
57
|
-
lib: Object.freeze(['esnext']),
|
|
58
|
-
module: 'NodeNext',
|
|
59
|
-
moduleResolution: 'NodeNext',
|
|
60
|
-
target: 'esnext',
|
|
61
|
-
checkJs: false
|
|
62
|
-
}),
|
|
63
|
-
include: Object.freeze(['src/**/*'])
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Глибока рівність для JSON-подібних значень (масиви — порядок важливий).
|
|
68
|
-
* @param {unknown} a
|
|
69
|
-
* @param {unknown} b
|
|
70
|
-
* @returns {boolean}
|
|
71
|
-
*/
|
|
72
|
-
function deepEqualJson(a, b) {
|
|
73
|
-
if (a === b) return true
|
|
74
|
-
if (a === null || b === null || typeof a !== typeof b) return false
|
|
75
|
-
if (typeof a !== 'object') return false
|
|
76
|
-
if (Array.isArray(a) !== Array.isArray(b)) return false
|
|
77
|
-
if (Array.isArray(a)) {
|
|
78
|
-
if (a.length !== b.length) return false
|
|
79
|
-
for (const [i, v] of a.entries()) {
|
|
80
|
-
if (!deepEqualJson(v, b[i])) return false
|
|
81
|
-
}
|
|
82
|
-
return true
|
|
83
|
-
}
|
|
84
|
-
const ao = /** @type {Record<string, unknown>} */ (a)
|
|
85
|
-
const bo = /** @type {Record<string, unknown>} */ (b)
|
|
86
|
-
const keysA = Object.keys(ao).sort()
|
|
87
|
-
const keysB = Object.keys(bo).sort()
|
|
88
|
-
if (keysA.length !== keysB.length) return false
|
|
89
|
-
for (const [i, k] of keysA.entries()) {
|
|
90
|
-
if (k !== keysB[i]) return false
|
|
91
|
-
if (!deepEqualJson(ao[k], bo[k])) return false
|
|
92
|
-
}
|
|
93
|
-
return true
|
|
94
|
-
}
|
|
95
|
-
|
|
96
60
|
/**
|
|
97
61
|
* Чи існує непорожній за змістом маркер каталогу `src/` (рекомендована структура js-run).
|
|
98
62
|
* @param {string} absPackageRoot абсолютний корінь пакета
|
|
99
|
-
* @returns {boolean}
|
|
63
|
+
* @returns {boolean} true, якщо `src/` існує і є каталогом
|
|
100
64
|
*/
|
|
101
65
|
function backendPackageHasSrcDir(absPackageRoot) {
|
|
102
66
|
const srcPath = join(absPackageRoot, 'src')
|
|
@@ -108,40 +72,29 @@ function backendPackageHasSrcDir(absPackageRoot) {
|
|
|
108
72
|
}
|
|
109
73
|
|
|
110
74
|
/**
|
|
111
|
-
*
|
|
75
|
+
* FS-existence для `jsconfig.json` у backend-пакеті з каталогом `src/` (cross-file:
|
|
76
|
+
* наявність каталогу + файла). Структуру самого `jsconfig.json` (canonical
|
|
77
|
+
* compilerOptions і include) валідує `npm/policy/js_run/jsconfig/`; її прогоняє
|
|
78
|
+
* `bun run lint-conftest`.
|
|
112
79
|
* @param {string} rootDir відносний шлях workspace
|
|
113
80
|
* @param {string} absPackageRoot абсолютний корінь пакета
|
|
114
81
|
* @param {string} label префікс `[pkg] `
|
|
115
|
-
* @param {(msg: string) => void} fail
|
|
116
|
-
* @param {(msg: string) => void} passFn
|
|
117
|
-
* @returns {
|
|
82
|
+
* @param {(msg: string) => void} fail callback для повідомлень про порушення
|
|
83
|
+
* @param {(msg: string) => void} passFn callback для повідомлень про успішну перевірку
|
|
84
|
+
* @returns {void}
|
|
118
85
|
*/
|
|
119
|
-
|
|
86
|
+
function checkBackendJsconfigWhenSrcPresent(rootDir, absPackageRoot, label, fail, passFn) {
|
|
120
87
|
if (!backendPackageHasSrcDir(absPackageRoot)) return
|
|
121
88
|
|
|
122
89
|
const jcPath = join(rootDir, 'jsconfig.json')
|
|
123
|
-
if (
|
|
90
|
+
if (existsSync(jcPath)) {
|
|
91
|
+
passFn(`${label}jsconfig.json є (структуру перевіряє bun run lint-conftest → js_run.jsconfig)`)
|
|
92
|
+
} else {
|
|
124
93
|
fail(
|
|
125
94
|
`${label}є каталог src/, але немає jsconfig.json — додай канонічний файл з js-run.mdc ` +
|
|
126
95
|
`(NodeNext, include: src/**/*).`
|
|
127
96
|
)
|
|
128
|
-
return
|
|
129
|
-
}
|
|
130
|
-
let parsed
|
|
131
|
-
try {
|
|
132
|
-
parsed = JSON.parse(await readFile(jcPath, 'utf8'))
|
|
133
|
-
} catch {
|
|
134
|
-
fail(`${label}jsconfig.json не вдалося розпарсити як JSON`)
|
|
135
|
-
return
|
|
136
|
-
}
|
|
137
|
-
if (!deepEqualJson(parsed, CANONICAL_BACKEND_JSCONFIG)) {
|
|
138
|
-
fail(
|
|
139
|
-
`${label}jsconfig.json не збігається з каноном js-run.mdc — заміни на шаблон з правила ` +
|
|
140
|
-
`(compilerOptions: lib esnext, module/moduleResolution NodeNext, target esnext, checkJs false; include: src/**/*).`
|
|
141
|
-
)
|
|
142
|
-
return
|
|
143
97
|
}
|
|
144
|
-
passFn(`${label}jsconfig.json узгоджено з js-run (пакет з src/)`)
|
|
145
98
|
}
|
|
146
99
|
|
|
147
100
|
/**
|
|
@@ -236,6 +189,52 @@ async function checkConnImports(absPackageRoot, sourcePaths, pkgJson, label, fai
|
|
|
236
189
|
return violations
|
|
237
190
|
}
|
|
238
191
|
|
|
192
|
+
/**
|
|
193
|
+
* Перевіряє правила нейминга та експортів для файлів усередині `#conn/`.
|
|
194
|
+
*
|
|
195
|
+
* Канон імені: `ql-<id>` для GraphQL, `(pg|mysql)-(read|write)[-<id>]` для БД (js-run.mdc,
|
|
196
|
+
* розділ «Нейминг файлів у `src/conn/`»). Експорт у файлі — лише іменований, з імʼям, що
|
|
197
|
+
* дорівнює camelCase від basename файла (`pg-write-contract.js` → `export const pgWriteContract`).
|
|
198
|
+
* @param {string} absPackageRoot абсолютний корінь пакета
|
|
199
|
+
* @param {string[]} sourcePaths абсолютні шляхи до файлів пакета
|
|
200
|
+
* @param {unknown} pkgJson розпарсений package.json пакета (або null)
|
|
201
|
+
* @param {string} label префікс повідомлення `[<pkg>] `
|
|
202
|
+
* @param {(msg: string) => void} fail callback при помилці
|
|
203
|
+
* @returns {Promise<number>} кількість порушень
|
|
204
|
+
*/
|
|
205
|
+
async function checkConnFileNamingAndExports(absPackageRoot, sourcePaths, pkgJson, label, fail) {
|
|
206
|
+
const connDir = resolveConnDirFromPackageJson(pkgJson)
|
|
207
|
+
let violations = 0
|
|
208
|
+
for (const absPath of sourcePaths) {
|
|
209
|
+
const rel = relPosix(absPackageRoot, absPath)
|
|
210
|
+
if (!isInsideConnDir(rel, connDir)) continue
|
|
211
|
+
if (!isConnFileRulesSourceFile(rel)) continue
|
|
212
|
+
// пропускаємо реекспортний барель `index.*` (якщо знадобиться) і прихований .d.ts
|
|
213
|
+
const base = rel.slice(rel.lastIndexOf('/') + 1)
|
|
214
|
+
if (base.startsWith('index.')) continue
|
|
215
|
+
|
|
216
|
+
const content = await readFile(absPath, 'utf8')
|
|
217
|
+
for (const v of findConnFileRuleViolations(content, rel)) {
|
|
218
|
+
violations++
|
|
219
|
+
if (v.kind === 'name') {
|
|
220
|
+
fail(
|
|
221
|
+
`${label}${rel} — назва файла в '${connDir}/' не відповідає канону js-run: ` +
|
|
222
|
+
`'ql-<id>', 'pg-{read|write}[-<id>]' або 'mysql-{read|write}[-<id>]' (kebab-case, [a-z0-9-])`
|
|
223
|
+
)
|
|
224
|
+
} else if (v.kind === 'default-export') {
|
|
225
|
+
fail(`${label}${rel} — 'export default' заборонений у '${connDir}/'; зроби іменований експорт`)
|
|
226
|
+
} else {
|
|
227
|
+
const found = v.foundNames?.length ? v.foundNames.join(', ') : '—'
|
|
228
|
+
fail(
|
|
229
|
+
`${label}${rel} — очікується іменований експорт 'export const ${v.expectedName} = …' ` +
|
|
230
|
+
`(camelCase від назви файла); знайдено: ${found}`
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return violations
|
|
236
|
+
}
|
|
237
|
+
|
|
239
238
|
/**
|
|
240
239
|
* Перевіряє правило «CheckEnv» для пакета.
|
|
241
240
|
* @param {string} absPackageRoot абсолютний корінь пакета
|
|
@@ -323,6 +322,14 @@ async function checkWorkspacePackage(rootDir, ignorePaths, workflows, fail, pass
|
|
|
323
322
|
passFn(`${label}імпорти підключень (bun#SQL / mssql / @nitra/graphql-request#GraphQLClient) лише в '${connDir}/'`)
|
|
324
323
|
}
|
|
325
324
|
|
|
325
|
+
const connFileViolations = await checkConnFileNamingAndExports(absPackageRoot, sourcePaths, pkgJson, label, fail)
|
|
326
|
+
if (connFileViolations === 0) {
|
|
327
|
+
const connDir = resolveConnDirFromPackageJson(pkgJson)
|
|
328
|
+
passFn(
|
|
329
|
+
`${label}файли в '${connDir}/' дотримують канону js-run: нейминг (ql-/pg-/mysql-…) і іменований експорт у camelCase від basename`
|
|
330
|
+
)
|
|
331
|
+
}
|
|
332
|
+
|
|
326
333
|
const envViolations = await checkProcessEnvUsage(absPackageRoot, sourcePaths, label, fail)
|
|
327
334
|
if (envViolations === 0) {
|
|
328
335
|
passFn(
|
|
@@ -390,15 +397,11 @@ async function loadPackageJsonAndCheckBunyanDeps(rootDir, label, fail) {
|
|
|
390
397
|
const pkgPath = join(rootDir, 'package.json')
|
|
391
398
|
if (!existsSync(pkgPath)) return null
|
|
392
399
|
const pkgJson = JSON.parse(await readFile(pkgPath, 'utf8'))
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
}
|
|
399
|
-
if (allDeps.bunyan) {
|
|
400
|
-
fail(`${label}bunyan знайдено — замінити на @nitra/pino`)
|
|
401
|
-
}
|
|
400
|
+
// Заборону `@nitra/bunyan` / `bunyan` у dependencies/devDependencies перенесено
|
|
401
|
+
// в Rego (`npm/policy/js_run/package_json/`); `bun run lint-conftest` запускає
|
|
402
|
+
// її по всіх workspace `package.json`. Тут лишилася лише AST-перевірка імпортів.
|
|
403
|
+
void label
|
|
404
|
+
void fail
|
|
402
405
|
return pkgJson
|
|
403
406
|
}
|
|
404
407
|
|
|
@@ -411,20 +414,15 @@ async function loadPackageJsonAndCheckBunyanDeps(rootDir, label, fail) {
|
|
|
411
414
|
* @param {(msg: string) => void} passFn успішне повідомлення
|
|
412
415
|
* @returns {Promise<void>} завершується після перевірки configmap
|
|
413
416
|
*/
|
|
414
|
-
|
|
417
|
+
function checkOtelConfigmap(rootDir, label, fail, passFn) {
|
|
415
418
|
const configmapPath = join(rootDir, 'k8s', 'base', 'configmap.yaml')
|
|
416
419
|
if (!existsSync(configmapPath)) return
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
passFn(`${
|
|
423
|
-
if (content.includes('service.name=') && content.includes('service.namespace=')) {
|
|
424
|
-
passFn(`${label}OTEL_RESOURCE_ATTRIBUTES містить service.name та service.namespace`)
|
|
425
|
-
} else {
|
|
426
|
-
fail(`${label}OTEL_RESOURCE_ATTRIBUTES має містити service.name=<name>,service.namespace=<namespace>`)
|
|
427
|
-
}
|
|
420
|
+
// Перевірку `OTEL_RESOURCE_ATTRIBUTES` має містити `service.name=` /
|
|
421
|
+
// `service.namespace=` перенесено в Rego (`npm/policy/js_run/configmap/`);
|
|
422
|
+
// `bun run lint-conftest` запускає її на всіх `k8s/base/configmap.yaml`.
|
|
423
|
+
void label
|
|
424
|
+
void fail
|
|
425
|
+
passFn(`${rootDir}/k8s/base/configmap.yaml є (OTEL — bun run lint-conftest → js_run.configmap)`)
|
|
428
426
|
}
|
|
429
427
|
|
|
430
428
|
/**
|