@nitra/cursor 1.8.206 → 1.8.207
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 +29 -0
- 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 +8 -8
- 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
|
@@ -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
|
@@ -65,9 +65,9 @@ const CANONICAL_BACKEND_JSCONFIG = Object.freeze({
|
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
67
|
* Глибока рівність для JSON-подібних значень (масиви — порядок важливий).
|
|
68
|
-
* @param {unknown} a
|
|
69
|
-
* @param {unknown} b
|
|
70
|
-
* @returns {boolean}
|
|
68
|
+
* @param {unknown} a перше значення
|
|
69
|
+
* @param {unknown} b друге значення
|
|
70
|
+
* @returns {boolean} true, якщо значення структурно ідентичні
|
|
71
71
|
*/
|
|
72
72
|
function deepEqualJson(a, b) {
|
|
73
73
|
if (a === b) return true
|
|
@@ -83,8 +83,8 @@ function deepEqualJson(a, b) {
|
|
|
83
83
|
}
|
|
84
84
|
const ao = /** @type {Record<string, unknown>} */ (a)
|
|
85
85
|
const bo = /** @type {Record<string, unknown>} */ (b)
|
|
86
|
-
const keysA = Object.keys(ao).
|
|
87
|
-
const keysB = Object.keys(bo).
|
|
86
|
+
const keysA = Object.keys(ao).toSorted()
|
|
87
|
+
const keysB = Object.keys(bo).toSorted()
|
|
88
88
|
if (keysA.length !== keysB.length) return false
|
|
89
89
|
for (const [i, k] of keysA.entries()) {
|
|
90
90
|
if (k !== keysB[i]) return false
|
|
@@ -96,7 +96,7 @@ function deepEqualJson(a, b) {
|
|
|
96
96
|
/**
|
|
97
97
|
* Чи існує непорожній за змістом маркер каталогу `src/` (рекомендована структура js-run).
|
|
98
98
|
* @param {string} absPackageRoot абсолютний корінь пакета
|
|
99
|
-
* @returns {boolean}
|
|
99
|
+
* @returns {boolean} true, якщо `src/` існує і є каталогом
|
|
100
100
|
*/
|
|
101
101
|
function backendPackageHasSrcDir(absPackageRoot) {
|
|
102
102
|
const srcPath = join(absPackageRoot, 'src')
|
|
@@ -112,8 +112,8 @@ function backendPackageHasSrcDir(absPackageRoot) {
|
|
|
112
112
|
* @param {string} rootDir відносний шлях workspace
|
|
113
113
|
* @param {string} absPackageRoot абсолютний корінь пакета
|
|
114
114
|
* @param {string} label префікс `[pkg] `
|
|
115
|
-
* @param {(msg: string) => void} fail
|
|
116
|
-
* @param {(msg: string) => void} passFn
|
|
115
|
+
* @param {(msg: string) => void} fail callback для повідомлень про порушення
|
|
116
|
+
* @param {(msg: string) => void} passFn callback для повідомлень про успішну перевірку
|
|
117
117
|
* @returns {Promise<void>}
|
|
118
118
|
*/
|
|
119
119
|
async function checkBackendJsconfigWhenSrcPresent(rootDir, absPackageRoot, label, fail, passFn) {
|
package/scripts/check-k8s.mjs
CHANGED
|
@@ -5648,8 +5648,8 @@ export function imageReplaceDeploymentPatchInfo(patchObj) {
|
|
|
5648
5648
|
|
|
5649
5649
|
/** @type {Array<{ containerIndex: number, newImage: string, opIndex: number }>} */
|
|
5650
5650
|
const ops = []
|
|
5651
|
-
for (
|
|
5652
|
-
const op = asPlainObject(
|
|
5651
|
+
for (const [i, element] of parsedArr.entries()) {
|
|
5652
|
+
const op = asPlainObject(element)
|
|
5653
5653
|
if (op === null) continue
|
|
5654
5654
|
const containerIndex = singleImageReplaceContainerIndex(op)
|
|
5655
5655
|
if (containerIndex === null) continue
|
|
@@ -6018,7 +6018,7 @@ function applyConversionsToDoc(doc, conversions) {
|
|
|
6018
6018
|
byPatch.set(c.index, slot)
|
|
6019
6019
|
}
|
|
6020
6020
|
|
|
6021
|
-
const sortedIdx =
|
|
6021
|
+
const sortedIdx = byPatch.keys().toSorted((a, b) => b - a)
|
|
6022
6022
|
for (const i of sortedIdx) {
|
|
6023
6023
|
const slot = byPatch.get(i)
|
|
6024
6024
|
if (slot === undefined) continue
|
|
@@ -6071,7 +6071,7 @@ function rewriteInlinePatchWithoutOps(patchText, opIndices) {
|
|
|
6071
6071
|
const seq = inner.contents
|
|
6072
6072
|
if (!isSeq(seq)) return null
|
|
6073
6073
|
|
|
6074
|
-
const toRemove =
|
|
6074
|
+
const toRemove = new Set(opIndices).toSorted((a, b) => b - a)
|
|
6075
6075
|
for (const i of toRemove) {
|
|
6076
6076
|
if (i < 0 || i >= seq.items.length) return null
|
|
6077
6077
|
seq.delete(i)
|
|
@@ -39,6 +39,9 @@ const TYPES_FILE_RE = /^\.\/types\/.+\.d\.(ts|mts)$/
|
|
|
39
39
|
/** Перший заголовок релізу у Keep a Changelog (`## [1.2.3]`). */
|
|
40
40
|
const CHANGELOG_FIRST_VERSION_RE = /^## \[([^\]]+)\]/m
|
|
41
41
|
|
|
42
|
+
/** Поле `version` у текстовому зрізі `package.json` (для `git show HEAD:npm/package.json`). */
|
|
43
|
+
const PACKAGE_JSON_VERSION_RE = /"version":\s*"([^"]+)"/u
|
|
44
|
+
|
|
42
45
|
/** Канонічний entrypoint типів для пакетів із вихідним `.js` під каталогом `npm/src` */
|
|
43
46
|
const TYPES_INDEX = './types/index.d.ts'
|
|
44
47
|
|
|
@@ -246,7 +249,7 @@ async function checkEmitTypesConfig(passFn, failFn) {
|
|
|
246
249
|
*/
|
|
247
250
|
/**
|
|
248
251
|
* Чи виконано `git` у корені робочого дерева.
|
|
249
|
-
* @returns {Promise<boolean>}
|
|
252
|
+
* @returns {Promise<boolean>} true, якщо процес запущено в межах git work tree
|
|
250
253
|
*/
|
|
251
254
|
async function gitInsideWorkTree() {
|
|
252
255
|
try {
|
|
@@ -273,7 +276,7 @@ async function gitDiffNameOnlyNpm() {
|
|
|
273
276
|
/**
|
|
274
277
|
* Поле `version` з `npm/package.json` на заданому git-ref (`HEAD:npm/package.json`).
|
|
275
278
|
* @param {string} refPath на кшталт `HEAD:npm/package.json`
|
|
276
|
-
* @returns {Promise<string | null>}
|
|
279
|
+
* @returns {Promise<string | null>} значення поля `version` або `null`, якщо ref недоступний
|
|
277
280
|
*/
|
|
278
281
|
async function gitShowNpmPackageVersionAt(refPath) {
|
|
279
282
|
try {
|
|
@@ -287,8 +290,8 @@ async function gitShowNpmPackageVersionAt(refPath) {
|
|
|
287
290
|
|
|
288
291
|
/**
|
|
289
292
|
* Версія з першого заголовка `## […]` у тексті CHANGELOG.
|
|
290
|
-
* @param {string} changelogText
|
|
291
|
-
* @returns {string | null}
|
|
293
|
+
* @param {string} changelogText вміст файлу CHANGELOG.md
|
|
294
|
+
* @returns {string | null} версія з першої секції або `null`, якщо заголовка немає
|
|
292
295
|
*/
|
|
293
296
|
function firstChangelogSectionVersion(changelogText) {
|
|
294
297
|
const m = changelogText.match(CHANGELOG_FIRST_VERSION_RE)
|
|
@@ -297,8 +300,8 @@ function firstChangelogSectionVersion(changelogText) {
|
|
|
297
300
|
|
|
298
301
|
/**
|
|
299
302
|
* Перший реліз у CHANGELOG має збігатися з `version` у `npm/package.json`.
|
|
300
|
-
* @param {(msg: string) => void} passFn
|
|
301
|
-
* @param {(msg: string) => void} failFn
|
|
303
|
+
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
304
|
+
* @param {(msg: string) => void} failFn callback при виявленому порушенні
|
|
302
305
|
* @returns {Promise<void>}
|
|
303
306
|
*/
|
|
304
307
|
async function checkChangelogTopMatchesPackageVersion(passFn, failFn) {
|
|
@@ -327,8 +330,8 @@ async function checkChangelogTopMatchesPackageVersion(passFn, failFn) {
|
|
|
327
330
|
|
|
328
331
|
/**
|
|
329
332
|
* Незакомічені зміни під `npm/` вимагають підвищення `version` відносно `HEAD`.
|
|
330
|
-
* @param {(msg: string) => void} passFn
|
|
331
|
-
* @param {(msg: string) => void} failFn
|
|
333
|
+
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
334
|
+
* @param {(msg: string) => void} failFn callback при виявленому порушенні
|
|
332
335
|
* @returns {Promise<void>}
|
|
333
336
|
*/
|
|
334
337
|
async function checkDirtyNpmRequiresVersionBump(passFn, failFn) {
|
|
@@ -360,6 +363,12 @@ async function checkDirtyNpmRequiresVersionBump(passFn, failFn) {
|
|
|
360
363
|
passFn(`npm/: незакомічені зміни під npm/ узгоджені з підвищенням version (${headVer} → ${cur})`)
|
|
361
364
|
}
|
|
362
365
|
|
|
366
|
+
/**
|
|
367
|
+
* Перевіряє npm-publish.yml workflow на наявність потрібних полів і кроків.
|
|
368
|
+
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
369
|
+
* @param {(msg: string) => void} failFn callback при виявленому порушенні
|
|
370
|
+
* @returns {Promise<void>}
|
|
371
|
+
*/
|
|
363
372
|
async function checkPublishWorkflow(passFn, failFn) {
|
|
364
373
|
const publishWf = '.github/workflows/npm-publish.yml'
|
|
365
374
|
if (!existsSync(publishWf)) {
|
package/scripts/check-php.mjs
CHANGED
|
@@ -1,79 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Перевіряє вимоги правила php.mdc для PHP-проєктів.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* **Що тут лишилося** (FS-existence — не покривається conftest):
|
|
5
|
+
* - наявність `composer.json` у корені;
|
|
6
|
+
* - наявність `.github/workflows/lint-php.yml`.
|
|
7
|
+
*
|
|
8
|
+
* **Що покрила Rego** (`bun run lint-conftest`):
|
|
9
|
+
* - `npm/policy/php/package_json/` — наявність скрипта `lint-php` у `package.json`;
|
|
10
|
+
* - `npm/policy/php/lint_php_yml/` — крок `run: bun run lint-php` у workflow.
|
|
8
11
|
*/
|
|
9
12
|
import { existsSync } from 'node:fs'
|
|
10
|
-
import { readFile } from 'node:fs/promises'
|
|
11
13
|
|
|
12
14
|
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
13
|
-
import { anyRunStepIncludes, parseWorkflowYaml } from './utils/gha-workflow.mjs'
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
|
-
* Перевіряє
|
|
17
|
-
* @
|
|
17
|
+
* Перевіряє відповідність проєкту правилам php.mdc.
|
|
18
|
+
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
18
19
|
*/
|
|
19
|
-
function
|
|
20
|
+
export async function check() {
|
|
21
|
+
const reporter = createCheckReporter()
|
|
20
22
|
const { pass, fail } = reporter
|
|
23
|
+
|
|
21
24
|
if (existsSync('composer.json')) {
|
|
22
25
|
pass('composer.json існує')
|
|
23
26
|
} else {
|
|
24
27
|
fail('composer.json не знайдено в корені — додай (php.mdc)')
|
|
25
28
|
}
|
|
26
|
-
}
|
|
27
29
|
|
|
28
|
-
/**
|
|
29
|
-
* Перевіряє кореневий `package.json` на скрипт `lint-php`.
|
|
30
|
-
* @param {import('./utils/check-reporter.mjs').CheckReporter} reporter репортер для збору результатів
|
|
31
|
-
*/
|
|
32
|
-
async function checkPackageJson(reporter) {
|
|
33
|
-
const { pass, fail } = reporter
|
|
34
30
|
if (!existsSync('package.json')) {
|
|
35
31
|
fail('package.json не знайдено в корені — додай (php.mdc)')
|
|
36
|
-
return
|
|
37
|
-
}
|
|
38
|
-
const pkg = JSON.parse(await readFile('package.json', 'utf8'))
|
|
39
|
-
const lintPhp = pkg.scripts?.['lint-php']
|
|
40
|
-
if (lintPhp) {
|
|
41
|
-
pass('package.json містить скрипт lint-php')
|
|
42
32
|
} else {
|
|
43
|
-
|
|
33
|
+
pass('package.json є (наявність lint-php перевіряє bun run lint-conftest → php.package_json)')
|
|
44
34
|
}
|
|
45
|
-
}
|
|
46
35
|
|
|
47
|
-
/**
|
|
48
|
-
* Перевіряє workflow `lint-php.yml`.
|
|
49
|
-
* @param {import('./utils/check-reporter.mjs').CheckReporter} reporter репортер для збору результатів
|
|
50
|
-
*/
|
|
51
|
-
async function checkWorkflow(reporter) {
|
|
52
|
-
const { pass, fail } = reporter
|
|
53
36
|
const wfPath = '.github/workflows/lint-php.yml'
|
|
54
|
-
if (
|
|
55
|
-
|
|
56
|
-
return
|
|
57
|
-
}
|
|
58
|
-
const content = await readFile(wfPath, 'utf8')
|
|
59
|
-
pass('lint-php.yml існує')
|
|
60
|
-
const root = parseWorkflowYaml(content)
|
|
61
|
-
const ok = root ? anyRunStepIncludes(root, 'bun run lint-php') : content.includes('bun run lint-php')
|
|
62
|
-
if (ok) {
|
|
63
|
-
pass('lint-php.yml викликає bun run lint-php')
|
|
37
|
+
if (existsSync(wfPath)) {
|
|
38
|
+
pass(`${wfPath} є (структуру перевіряє bun run lint-conftest → php.lint_php_yml)`)
|
|
64
39
|
} else {
|
|
65
|
-
fail(
|
|
40
|
+
fail(`${wfPath} не існує — створи згідно php.mdc`)
|
|
66
41
|
}
|
|
67
|
-
}
|
|
68
42
|
|
|
69
|
-
/**
|
|
70
|
-
* Перевіряє відповідність проєкту правилам php.mdc.
|
|
71
|
-
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
72
|
-
*/
|
|
73
|
-
export async function check() {
|
|
74
|
-
const reporter = createCheckReporter()
|
|
75
|
-
checkComposer(reporter)
|
|
76
|
-
await checkPackageJson(reporter)
|
|
77
|
-
await checkWorkflow(reporter)
|
|
78
43
|
return reporter.getExitCode()
|
|
79
44
|
}
|
|
@@ -1,74 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Перевіряє CSS/SCSS лінт за правилом style-lint.mdc.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* **Що тут лишилося** (FS / VSCode-конфіги — не покривається conftest):
|
|
5
|
+
* - наявність зовнішнього файлу конфігу stylelint (`.stylelintrc.*`,
|
|
6
|
+
* `stylelint.config.js`) як альтернатива полю `stylelint` у `package.json`
|
|
7
|
+
* (cross-file: треба знати, чи є поле, чи немає);
|
|
8
|
+
* - `.stylelintignore` у корені;
|
|
9
|
+
* - `.vscode/extensions.json` recommendation `stylelint.vscode-stylelint`;
|
|
10
|
+
* - `.vscode/settings.json` `css.validate` / `scss.validate` / `less.validate: false`.
|
|
11
|
+
*
|
|
12
|
+
* **Що покрила Rego** (`bun run lint-conftest`):
|
|
13
|
+
* - `npm/policy/style_lint/package_json/` — скрипт `lint-style` через `npx stylelint`,
|
|
14
|
+
* `@nitra/stylelint-config` у `devDependencies`, поле `stylelint.extends`;
|
|
15
|
+
* - `npm/policy/style_lint/lint_style_yml/` — `npx stylelint` у `run` workflow.
|
|
7
16
|
*/
|
|
8
17
|
import { existsSync } from 'node:fs'
|
|
9
18
|
import { readFile } from 'node:fs/promises'
|
|
10
19
|
|
|
11
20
|
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
12
|
-
import { anyRunStepIncludesStylelint, parseWorkflowYaml } from './utils/gha-workflow.mjs'
|
|
13
21
|
|
|
14
22
|
/**
|
|
15
|
-
*
|
|
23
|
+
* Альтернатива полю `stylelint` у `package.json` — зовнішній файл конфігу. Якщо
|
|
24
|
+
* поля немає і файлу немає, фейлимося; якщо є хоч щось — пропускаємо. Поле
|
|
25
|
+
* `stylelint.extends == "@nitra/stylelint-config"` сам формат — у Rego.
|
|
26
|
+
* @param {import('./utils/check-reporter.mjs').CheckReporter} reporter репортер
|
|
16
27
|
*/
|
|
17
|
-
async function
|
|
28
|
+
async function checkStylelintConfigPresence(reporter) {
|
|
18
29
|
const { pass, fail } = reporter
|
|
19
30
|
if (!existsSync('package.json')) return
|
|
20
31
|
const pkg = JSON.parse(await readFile('package.json', 'utf8'))
|
|
21
|
-
|
|
22
|
-
const lintStyle = pkg.scripts?.['lint-style']
|
|
23
|
-
if (lintStyle) {
|
|
24
|
-
pass('package.json містить скрипт lint-style')
|
|
25
|
-
if (String(lintStyle).includes('npx stylelint')) {
|
|
26
|
-
pass('lint-style викликає stylelint через npx')
|
|
27
|
-
} else {
|
|
28
|
-
fail("lint-style має викликати stylelint через npx — наприклад: npx stylelint '**/*.{css,scss,vue}' --fix")
|
|
29
|
-
}
|
|
30
|
-
} else {
|
|
31
|
-
fail('package.json не містить скрипт "lint-style"')
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (pkg.devDependencies?.['@nitra/stylelint-config']) {
|
|
35
|
-
pass('@nitra/stylelint-config є в devDependencies')
|
|
36
|
-
} else {
|
|
37
|
-
fail('@nitra/stylelint-config відсутній — bun add -d @nitra/stylelint-config')
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const stylelintCfg = pkg.stylelint
|
|
32
|
+
const hasField = pkg.stylelint && typeof pkg.stylelint === 'object'
|
|
41
33
|
const hasExternalCfg =
|
|
42
34
|
existsSync('.stylelintrc.json') || existsSync('.stylelintrc.js') || existsSync('stylelint.config.js')
|
|
43
|
-
if (
|
|
44
|
-
pass('package.json
|
|
45
|
-
} else if (hasExternalCfg) {
|
|
46
|
-
pass('Окремий файл конфігу stylelint існує')
|
|
35
|
+
if (hasField || hasExternalCfg) {
|
|
36
|
+
pass('Конфіг stylelint є — у package.json або окремим файлом')
|
|
47
37
|
} else {
|
|
48
38
|
fail('Немає конфігу stylelint — додай "stylelint": { "extends": "@nitra/stylelint-config" } до package.json')
|
|
49
39
|
}
|
|
50
40
|
}
|
|
51
41
|
|
|
52
|
-
/**
|
|
53
|
-
* @param {import('./utils/check-reporter.mjs').CheckReporter} reporter репортер для збору результатів
|
|
54
|
-
*/
|
|
55
|
-
async function checkStylelintWorkflow(reporter) {
|
|
56
|
-
const { pass, fail } = reporter
|
|
57
|
-
if (!existsSync('.github/workflows/lint-style.yml')) {
|
|
58
|
-
fail('.github/workflows/lint-style.yml не існує — створи його')
|
|
59
|
-
return
|
|
60
|
-
}
|
|
61
|
-
const content = await readFile('.github/workflows/lint-style.yml', 'utf8')
|
|
62
|
-
pass('lint-style.yml існує')
|
|
63
|
-
const root = parseWorkflowYaml(content)
|
|
64
|
-
const ok = root ? anyRunStepIncludesStylelint(root) : content.includes('npx stylelint')
|
|
65
|
-
if (ok) {
|
|
66
|
-
pass('lint-style.yml містить npx stylelint у кроці run')
|
|
67
|
-
} else {
|
|
68
|
-
fail("lint-style.yml має викликати stylelint у CI через npx — наприклад: npx stylelint '**/*.{css,scss,vue}' --fix")
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
42
|
/**
|
|
73
43
|
* @param {import('./utils/check-reporter.mjs').CheckReporter} reporter репортер для збору результатів
|
|
74
44
|
*/
|
|
@@ -104,7 +74,7 @@ export async function check() {
|
|
|
104
74
|
const reporter = createCheckReporter()
|
|
105
75
|
const { pass, fail } = reporter
|
|
106
76
|
|
|
107
|
-
await
|
|
77
|
+
await checkStylelintConfigPresence(reporter)
|
|
108
78
|
|
|
109
79
|
if (existsSync('.stylelintignore')) {
|
|
110
80
|
pass('.stylelintignore існує')
|
|
@@ -112,7 +82,13 @@ export async function check() {
|
|
|
112
82
|
fail('.stylelintignore не існує — створи з вмістом: dist/')
|
|
113
83
|
}
|
|
114
84
|
|
|
115
|
-
|
|
85
|
+
const wfPath = '.github/workflows/lint-style.yml'
|
|
86
|
+
if (existsSync(wfPath)) {
|
|
87
|
+
pass(`${wfPath} є (структуру перевіряє bun run lint-conftest → style_lint.lint_style_yml)`)
|
|
88
|
+
} else {
|
|
89
|
+
fail(`${wfPath} не існує — створи його`)
|
|
90
|
+
}
|
|
91
|
+
|
|
116
92
|
await checkVscodeStylelint(reporter)
|
|
117
93
|
|
|
118
94
|
return reporter.getExitCode()
|