@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
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()
|
package/scripts/check-text.mjs
CHANGED
|
@@ -1,62 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Перевіряє текстовий стек і форматування за правилом text.mdc.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* **Що тут лишилося** (FS / VSCode-конфіги / markdown / лінт-скрипт):
|
|
5
|
+
* - `.v8rignore` (текстовий формат, рядки шляхів);
|
|
6
|
+
* - `.vscode/extensions.json` рекомендації (markdownlint, oxc, shellcheck) і
|
|
7
|
+
* `.vscode/settings.json` (`editor.formatOnSave`, `[lang].editor.defaultFormatter`);
|
|
8
|
+
* - наявність FS-файлів `.oxfmtrc.json`, `.cspell.json`, `.markdownlint-cli2.jsonc`,
|
|
9
|
+
* `package.json` (саме *існування* — структуру вже валідує Rego);
|
|
10
|
+
* - конфіги Prettier у корені (заборонено — FS);
|
|
11
|
+
* - абзац про український апостроф у `.cursor/rules/n-text.mdc` /
|
|
12
|
+
* `npm/mdc/text.mdc` (markdown-текст, не JSON/YAML);
|
|
13
|
+
* - складна валідація скрипта `lint-text` (cspell, markdownlint, v8r у трьох
|
|
14
|
+
* варіантах, run-shellcheck-text.mjs, обовʼязкові glob-и);
|
|
15
|
+
* - workflow `lint-text.yml` має крок `bun run lint-text`.
|
|
7
16
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* `.
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
+
* **Що покрила Rego** (`bun run lint-conftest`):
|
|
18
|
+
* - `npm/policy/text/oxfmtrc/` — обовʼязкові ключі `.oxfmtrc.json` і канонічні
|
|
19
|
+
* значення (semi/singleQuote/tabWidth/useTabs/printWidth) + `ignorePatterns`
|
|
20
|
+
* канонічні glob-и;
|
|
21
|
+
* - `npm/policy/text/cspell/` — `.cspell.json` `version "0.2"`, `language`,
|
|
22
|
+
* імпорт `@nitra/cspell-dict`, заборона `@cspell/dict-*`, обовʼязкові
|
|
23
|
+
* `ignorePaths`;
|
|
24
|
+
* - `npm/policy/text/markdownlint/` — `.markdownlint-cli2.jsonc` `gitignore: true`
|
|
25
|
+
* (працює лише якщо файл — валідний JSON без коментарів);
|
|
26
|
+
* - `npm/policy/text/package_json/` — заборона Prettier (`prettier` поле +
|
|
27
|
+
* `prettier`/`@nitra/prettier-config` у залежностях), `@nitra/cspell-dict ^2.0.0+`
|
|
28
|
+
* у `devDependencies`, заборона `markdownlint-cli2` у залежностях.
|
|
29
|
+
* - `npm/policy/bun/package_json/` — у `devDependencies` лише `@nitra/*`
|
|
30
|
+
* (раніше дублювалося тут).
|
|
17
31
|
*/
|
|
18
32
|
import { existsSync } from 'node:fs'
|
|
19
33
|
import { readFile } from 'node:fs/promises'
|
|
20
34
|
|
|
21
|
-
import { isAllowedRootDevDependency } from './check-bun.mjs'
|
|
22
35
|
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
23
36
|
import { anyRunStepIncludes, parseWorkflowYaml } from './utils/gha-workflow.mjs'
|
|
24
37
|
|
|
25
|
-
const WORKSPACE_STAR_RE = /^workspace:\*/
|
|
26
|
-
const VERSION_PREFIX_RE = /^[\^~>=<]+\s*/
|
|
27
|
-
const SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)/
|
|
28
|
-
|
|
29
38
|
/** Заголовок абзацу про апостроф у text.mdc / n-text.mdc. */
|
|
30
39
|
const UK_APOSTROPHE_HEADING = '**Український апостроф:**'
|
|
31
40
|
|
|
32
|
-
/** Мінімальні glob-и в `ignorePatterns` у `.oxfmtrc.json` (text.mdc) — додаткові патерни локально дозволені. */
|
|
33
|
-
const OXFMT_REQUIRED_IGNORE_PATTERNS = ['**/hasura/metadata/**', '**/schema.graphql', '**/auto-imports.d.ts']
|
|
34
|
-
|
|
35
|
-
/** Канонічні записи `ignorePaths` у `.cspell.json` (text.mdc) — кожен має бути присутнім. */
|
|
36
|
-
const CSPELL_REQUIRED_IGNORE_PATHS = [
|
|
37
|
-
'**/node_modules/**',
|
|
38
|
-
'**/vscode-extension/**',
|
|
39
|
-
'**/.git/**',
|
|
40
|
-
'.vscode',
|
|
41
|
-
'report',
|
|
42
|
-
'*.svg',
|
|
43
|
-
'**/k8s/**/*.yaml'
|
|
44
|
-
]
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Чи діапазон версії `@nitra/cspell-dict` у package.json означає лінію 2.0.0+ (з цієї версії словники входять у пакет).
|
|
48
|
-
* @param {string|undefined} range наприклад "^2.0.0"
|
|
49
|
-
* @returns {boolean} true якщо мажорна версія >= 2
|
|
50
|
-
*/
|
|
51
|
-
function cspellDictVersionAtLeast200(range) {
|
|
52
|
-
if (typeof range !== 'string' || !range.trim()) return false
|
|
53
|
-
const cleaned = range.trim().replace(WORKSPACE_STAR_RE, '').replace(VERSION_PREFIX_RE, '')
|
|
54
|
-
const m = cleaned.match(SEMVER_RE)
|
|
55
|
-
if (!m) return false
|
|
56
|
-
const major = Number(m[1])
|
|
57
|
-
return major >= 2
|
|
58
|
-
}
|
|
59
|
-
|
|
60
41
|
/**
|
|
61
42
|
* Перевіряє абзац про український апостроф у вмісті правила text.
|
|
62
43
|
* @param {string} filePath шлях до файлу (для повідомлень)
|
|
@@ -74,8 +55,8 @@ function verifyUkApostropheRuleParagraph(filePath, body, failFn, passFn) {
|
|
|
74
55
|
failFn(`${filePath}: абзац про апостроф має містити позначки U+0027 та U+2019`)
|
|
75
56
|
return
|
|
76
57
|
}
|
|
77
|
-
if (!body.includes('
|
|
78
|
-
failFn(`${filePath}: у прикладі має бути типографський символ U+2019 (
|
|
58
|
+
if (!body.includes('’')) {
|
|
59
|
+
failFn(`${filePath}: у прикладі має бути типографський символ U+2019 (’)`)
|
|
79
60
|
return
|
|
80
61
|
}
|
|
81
62
|
passFn(`${filePath}: абзац про український апостроф на місці`)
|
|
@@ -174,122 +155,38 @@ async function checkVscodeText(passFn, failFn) {
|
|
|
174
155
|
}
|
|
175
156
|
|
|
176
157
|
/**
|
|
177
|
-
*
|
|
158
|
+
* FS-existence стек текстових конфігів. Контент-валідація — у Rego
|
|
159
|
+
* (`text.oxfmtrc`, `text.cspell`, `text.markdownlint`).
|
|
178
160
|
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
179
161
|
* @param {(msg: string) => void} failFn callback при помилці
|
|
162
|
+
* @returns {Promise<void>}
|
|
180
163
|
*/
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
'printWidth',
|
|
190
|
-
'bracketSpacing',
|
|
191
|
-
'bracketSameLine',
|
|
192
|
-
'semi',
|
|
193
|
-
'singleQuote',
|
|
194
|
-
'tabWidth',
|
|
195
|
-
'trailingComma',
|
|
196
|
-
'useTabs'
|
|
197
|
-
]
|
|
198
|
-
const missing = requiredKeys.filter(k => !(k in cfg))
|
|
199
|
-
if (missing.length === 0) {
|
|
200
|
-
passFn('.oxfmtrc.json містить всі обовʼязкові ключі')
|
|
201
|
-
} else {
|
|
202
|
-
failFn(`.oxfmtrc.json відсутні ключі: ${missing.join(', ')}`)
|
|
203
|
-
}
|
|
204
|
-
if (cfg.semi !== false) failFn('.oxfmtrc.json: semi має бути false')
|
|
205
|
-
if (cfg.singleQuote !== true) failFn('.oxfmtrc.json: singleQuote має бути true')
|
|
206
|
-
if (cfg.tabWidth !== 2) failFn('.oxfmtrc.json: tabWidth має бути 2')
|
|
207
|
-
if (cfg.useTabs !== false) failFn('.oxfmtrc.json: useTabs має бути false')
|
|
208
|
-
if (cfg.printWidth !== 120) failFn('.oxfmtrc.json: printWidth має бути 120')
|
|
209
|
-
|
|
210
|
-
if (Array.isArray(cfg.ignorePatterns)) {
|
|
211
|
-
const set = new Set(cfg.ignorePatterns)
|
|
212
|
-
const missingPatterns = OXFMT_REQUIRED_IGNORE_PATTERNS.filter(p => !set.has(p))
|
|
213
|
-
if (missingPatterns.length === 0) {
|
|
214
|
-
passFn('.oxfmtrc.json: ignorePatterns містить hasura/metadata, schema.graphql і auto-imports.d.ts')
|
|
164
|
+
function checkTextConfigsExistence(passFn, failFn) {
|
|
165
|
+
for (const [path, mdcRef] of [
|
|
166
|
+
['.oxfmtrc.json', 'text.oxfmtrc'],
|
|
167
|
+
['.cspell.json', 'text.cspell'],
|
|
168
|
+
['.markdownlint-cli2.jsonc', 'text.markdownlint']
|
|
169
|
+
]) {
|
|
170
|
+
if (existsSync(path)) {
|
|
171
|
+
passFn(`${path} є (структуру перевіряє bun run lint-conftest → ${mdcRef})`)
|
|
215
172
|
} else {
|
|
216
|
-
failFn(
|
|
217
|
-
`.oxfmtrc.json ignorePatterns: додай відсутні елементи: ${missingPatterns.join(', ')} (канонічний приклад у text.mdc)`
|
|
218
|
-
)
|
|
173
|
+
failFn(`${path} не існує — створи згідно n-text.mdc`)
|
|
219
174
|
}
|
|
220
|
-
} else {
|
|
221
|
-
failFn(`.oxfmtrc.json: додай масив ignorePatterns з ${OXFMT_REQUIRED_IGNORE_PATTERNS.join(', ')} (див. text.mdc)`)
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* Перевіряє залежності package.json для текстового стека.
|
|
227
|
-
* @param {{ dependencies?: Record<string, string>, devDependencies?: Record<string, string>, prettier?: unknown }} pkg розібраний package.json
|
|
228
|
-
* @param {Record<string, string>} devDeps devDependencies з package.json
|
|
229
|
-
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
230
|
-
* @param {(msg: string) => void} failFn callback при помилці
|
|
231
|
-
*/
|
|
232
|
-
function checkPackageJsonTextDepsUsage(pkg, devDeps, passFn, failFn) {
|
|
233
|
-
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies }
|
|
234
|
-
for (const dep of ['prettier', '@nitra/prettier-config']) {
|
|
235
|
-
if (allDeps[dep]) failFn(`package.json містить залежність ${dep} — видали її`)
|
|
236
|
-
}
|
|
237
|
-
if (pkg.prettier) failFn('package.json містить поле "prettier" — видали його')
|
|
238
|
-
|
|
239
|
-
const nonNitraDev = Object.keys(devDeps).filter(n => !isAllowedRootDevDependency(n))
|
|
240
|
-
if (nonNitraDev.length > 0) {
|
|
241
|
-
failFn(
|
|
242
|
-
`Кореневі devDependencies: дозволені лише @nitra/* — прибери або перенеси: ${nonNitraDev.join(', ')} (bun.mdc)`
|
|
243
|
-
)
|
|
244
|
-
} else {
|
|
245
|
-
passFn('Кореневі devDependencies лише @nitra/*')
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const cspellRange = devDeps['@nitra/cspell-dict']
|
|
249
|
-
if (!cspellRange) {
|
|
250
|
-
failFn('@nitra/cspell-dict у devDependencies обовʼязковий для cspell — bun add -d @nitra/cspell-dict@^2.0.0')
|
|
251
|
-
} else if (cspellDictVersionAtLeast200(cspellRange)) {
|
|
252
|
-
passFn('@nitra/cspell-dict ^2.0.0+')
|
|
253
|
-
} else {
|
|
254
|
-
failFn('@nitra/cspell-dict має бути ^2.0.0 або новіший (словники зібрані в пакеті з 2.x)')
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
if (devDeps['markdownlint-cli2'] || (pkg.dependencies || {})['markdownlint-cli2']) {
|
|
258
|
-
failFn(
|
|
259
|
-
'markdownlint-cli2 не додавай у dependencies/devDependencies — лише bunx у lint-text (n-text.mdc); прибери з package.json і bun i'
|
|
260
|
-
)
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Перевіряє відсутність прямих імпортів `@cspell/dict-*` у .cspell.json.
|
|
266
|
-
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
267
|
-
* @param {(msg: string) => void} failFn callback при помилці
|
|
268
|
-
*/
|
|
269
|
-
async function checkCspellJsonDictImports(passFn, failFn) {
|
|
270
|
-
if (!existsSync('.cspell.json')) return
|
|
271
|
-
const cfg = JSON.parse(await readFile('.cspell.json', 'utf8'))
|
|
272
|
-
const dictImports = (cfg.import || []).filter(i => typeof i === 'string' && i.includes('@cspell/dict-'))
|
|
273
|
-
if (dictImports.length > 0) {
|
|
274
|
-
failFn(
|
|
275
|
-
`.cspell.json не має імпортувати @cspell/dict-* (${dictImports.join(', ')}) — використовуй лише @nitra/cspell-dict/cspell-ext.json`
|
|
276
|
-
)
|
|
277
|
-
} else {
|
|
278
|
-
passFn('.cspell.json без прямих імпортів @cspell/dict-*')
|
|
279
175
|
}
|
|
176
|
+
return Promise.resolve()
|
|
280
177
|
}
|
|
281
178
|
|
|
282
179
|
/**
|
|
283
|
-
* Перевіряє package.json для текстового
|
|
180
|
+
* Перевіряє package.json для текстового стека: складний `lint-text` скрипт і
|
|
181
|
+
* виклик `bun run lint-text` у відповідному workflow. Решта (Prettier-заборона,
|
|
182
|
+
* `@nitra/cspell-dict ^2.0.0+`, заборона `markdownlint-cli2` у залежностях,
|
|
183
|
+
* `@nitra/*` гейт) — у Rego (`text.package_json`, `bun.package_json`).
|
|
284
184
|
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
285
185
|
* @param {(msg: string) => void} failFn callback при помилці
|
|
286
186
|
*/
|
|
287
187
|
async function checkPackageJsonText(passFn, failFn) {
|
|
288
188
|
if (!existsSync('package.json')) return
|
|
289
189
|
const pkg = JSON.parse(await readFile('package.json', 'utf8'))
|
|
290
|
-
const devDeps = pkg.devDependencies || {}
|
|
291
|
-
|
|
292
|
-
checkPackageJsonTextDepsUsage(pkg, devDeps, passFn, failFn)
|
|
293
190
|
checkLintTextScript(pkg.scripts?.['lint-text'], passFn, failFn)
|
|
294
191
|
|
|
295
192
|
if (existsSync('.github/workflows/lint-text.yml')) {
|
|
@@ -304,8 +201,6 @@ async function checkPackageJsonText(passFn, failFn) {
|
|
|
304
201
|
} else {
|
|
305
202
|
failFn('.github/workflows/lint-text.yml не існує — створи згідно n-text.mdc')
|
|
306
203
|
}
|
|
307
|
-
|
|
308
|
-
await checkCspellJsonDictImports(passFn, failFn)
|
|
309
204
|
}
|
|
310
205
|
|
|
311
206
|
/**
|
|
@@ -344,71 +239,7 @@ function checkLintTextScript(lintText, passFn, failFn) {
|
|
|
344
239
|
}
|
|
345
240
|
|
|
346
241
|
/**
|
|
347
|
-
* Перевіряє .
|
|
348
|
-
* @param {(msg: string) => void} pass callback при успішній перевірці
|
|
349
|
-
* @param {(msg: string) => void} fail callback при помилці
|
|
350
|
-
*/
|
|
351
|
-
async function checkMarkdownlintConfig(pass, fail) {
|
|
352
|
-
if (!existsSync('.markdownlint-cli2.jsonc')) {
|
|
353
|
-
fail('.markdownlint-cli2.jsonc не існує — створи згідно n-text.mdc')
|
|
354
|
-
return
|
|
355
|
-
}
|
|
356
|
-
try {
|
|
357
|
-
const ml = JSON.parse(await readFile('.markdownlint-cli2.jsonc', 'utf8'))
|
|
358
|
-
pass('.markdownlint-cli2.jsonc існує і є валідним JSON')
|
|
359
|
-
if (ml.gitignore === true) {
|
|
360
|
-
pass('.markdownlint-cli2.jsonc: gitignore увімкнено')
|
|
361
|
-
} else {
|
|
362
|
-
fail('.markdownlint-cli2.jsonc: додай на верхньому рівні "gitignore": true (див. n-text.mdc)')
|
|
363
|
-
}
|
|
364
|
-
} catch {
|
|
365
|
-
fail('.markdownlint-cli2.jsonc — невалідний JSON; перевір синтаксис')
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
/**
|
|
370
|
-
* Перевіряє .cspell.json на версію, мову, імпорт і ignorePaths.
|
|
371
|
-
* @param {(msg: string) => void} pass callback при успішній перевірці
|
|
372
|
-
* @param {(msg: string) => void} fail callback при помилці
|
|
373
|
-
*/
|
|
374
|
-
async function checkCspellConfig(pass, fail) {
|
|
375
|
-
if (!existsSync('.cspell.json')) {
|
|
376
|
-
fail('.cspell.json не існує — створи його')
|
|
377
|
-
return
|
|
378
|
-
}
|
|
379
|
-
const cfg = JSON.parse(await readFile('.cspell.json', 'utf8'))
|
|
380
|
-
if (cfg.version === '0.2') {
|
|
381
|
-
pass('.cspell.json version: 0.2')
|
|
382
|
-
} else {
|
|
383
|
-
fail('.cspell.json version має бути "0.2"')
|
|
384
|
-
}
|
|
385
|
-
if (cfg.language) {
|
|
386
|
-
pass(`.cspell.json language: "${cfg.language}"`)
|
|
387
|
-
} else {
|
|
388
|
-
fail('.cspell.json не містить поле language')
|
|
389
|
-
}
|
|
390
|
-
if ((cfg.import || []).some(i => i.includes('@nitra/cspell-dict'))) {
|
|
391
|
-
pass('.cspell.json імпортує @nitra/cspell-dict')
|
|
392
|
-
} else {
|
|
393
|
-
fail('.cspell.json не імпортує @nitra/cspell-dict/cspell-ext.json')
|
|
394
|
-
}
|
|
395
|
-
if (Array.isArray(cfg.ignorePaths)) {
|
|
396
|
-
pass('.cspell.json містить ignorePaths')
|
|
397
|
-
} else {
|
|
398
|
-
fail('.cspell.json не містить ignorePaths')
|
|
399
|
-
}
|
|
400
|
-
if (Array.isArray(cfg.ignorePaths)) {
|
|
401
|
-
const missing = CSPELL_REQUIRED_IGNORE_PATHS.filter(p => !cfg.ignorePaths.includes(p))
|
|
402
|
-
if (missing.length === 0) {
|
|
403
|
-
pass(`.cspell.json ignorePaths містить усі обовʼязкові glob-и з text.mdc`)
|
|
404
|
-
} else {
|
|
405
|
-
fail(`.cspell.json ignorePaths бракує за замовчанням: ${missing.join(', ')} (див. text.mdc)`)
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
/**
|
|
411
|
-
* Перевіряє відповідність проєкту правилам text.mdc (oxfmt, cspell, shellcheck у lint-text, markdownlint через bunx, v8r)
|
|
242
|
+
* Перевіряє відповідність проєкту правилам text.mdc.
|
|
412
243
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
413
244
|
*/
|
|
414
245
|
export async function check() {
|
|
@@ -417,15 +248,12 @@ export async function check() {
|
|
|
417
248
|
|
|
418
249
|
await checkV8rIgnore(pass, fail)
|
|
419
250
|
await checkVscodeText(pass, fail)
|
|
420
|
-
await
|
|
251
|
+
await checkTextConfigsExistence(pass, fail)
|
|
421
252
|
|
|
422
253
|
for (const f of ['.prettierrc', '.prettierrc.json', '.prettierrc.js', 'prettier.config.js', '.prettierrc.yml']) {
|
|
423
254
|
if (existsSync(f)) fail(`Знайдено конфіг prettier: ${f} — видали його`)
|
|
424
255
|
}
|
|
425
256
|
|
|
426
|
-
await checkMarkdownlintConfig(pass, fail)
|
|
427
|
-
await checkCspellConfig(pass, fail)
|
|
428
|
-
|
|
429
257
|
const textRulePaths = ['.cursor/rules/n-text.mdc', 'npm/mdc/text.mdc'].filter(p => existsSync(p))
|
|
430
258
|
if (textRulePaths.length === 0) {
|
|
431
259
|
pass('n-text.mdc / npm/mdc/text.mdc відсутні — перевірку абзацу про апостроф пропущено')
|