@nitra/cursor 1.8.179 → 1.8.184
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 +44 -5
- package/mdc/ga.mdc +23 -1
- package/mdc/js-run.mdc +35 -1
- package/package.json +4 -4
- package/scripts/check-abie.mjs +1 -0
- package/scripts/check-changelog.mjs +49 -50
- package/scripts/check-docker.mjs +1 -1
- package/scripts/check-ga.mjs +69 -8
- package/scripts/check-graphql.mjs +1 -0
- package/scripts/check-hasura.mjs +1 -0
- package/scripts/check-image.mjs +11 -7
- package/scripts/check-js-bun-db.mjs +3 -22
- package/scripts/check-js-lint.mjs +3 -1
- package/scripts/check-js-mssql.mjs +5 -23
- package/scripts/check-js-run.mjs +63 -3
- package/scripts/check-k8s.mjs +33 -32
- package/scripts/check-nginx-default-tpl.mjs +3 -0
- package/scripts/check-npm-module.mjs +1 -0
- package/scripts/check-vue.mjs +17 -10
- package/scripts/claude-stop-hook.mjs +24 -21
- package/scripts/lint-ga.mjs +0 -1
- package/scripts/rename-yaml-extensions.mjs +1 -0
- package/scripts/run-docker.mjs +1 -0
- package/scripts/run-k8s.mjs +1 -0
- package/scripts/sync-claude-config.mjs +28 -28
- package/scripts/utils/ast-scan-utils.mjs +1 -1
- package/scripts/utils/bun-sql-scan.mjs +1 -2
- package/scripts/utils/depcheck-workflow.mjs +188 -0
- package/scripts/utils/find-package-json-paths.mjs +30 -0
- package/scripts/utils/load-cursor-config.mjs +3 -1
- package/scripts/utils/oxlint-canonical.json +3 -16
- package/scripts/utils/walkDir.mjs +11 -9
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,48 @@
|
|
|
4
4
|
|
|
5
5
|
Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
|
|
6
6
|
|
|
7
|
+
## [1.8.184] - 2026-05-06
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `check-js-run.mjs`: програмна перевірка нового правила «depcheck у GitHub Actions з path-фільтром». Для кожного backend workspace-пакета сканується `.github/workflows/*.yml`; якщо `on.push.paths` або `on.pull_request.paths` містить glob, що починається з `<rootDir>/`, у job очікується крок `npx depcheck` з `working-directory: <rootDir>` і `--ignores`, що містить мінімум `graphql,bun` (інші значення допустимі). Логіка — у новому `scripts/utils/depcheck-workflow.mjs` (парсинг `--ignores="…"` з підтримкою single/double-quote і unquoted формату; класифікація `missing` / `wrong-cwd` / `missing-ignores`).
|
|
12
|
+
- `check-js-run-fixture.test.mjs`: 9 нових кейсів — нема `.github/workflows/`, глобальні paths без скоупу пакета, scoped-paths без depcheck (fail), depcheck з неправильним `working-directory` (fail), без `--ignores` (fail), `--ignores` без `bun` (fail), валідний з extra-ignores (pass), вкладений `cron-jobs/foo/src/**` як scope (pass).
|
|
13
|
+
- `.github/workflows/npm-publish.yml`: додано власний крок `npx depcheck --ignores="graphql,bun,bun:test,@nitra/cursor"` з `working-directory: npm`, щоб репо `@nitra/cursor` саме відповідало новому правилу js-run (`paths: ['npm/**']` обмежено пакетом `npm`); extra-ignores потрібні для self-reference `@nitra/cursor` у devDependencies та для `bun:test` як bun-built-in.
|
|
14
|
+
|
|
15
|
+
## [1.8.183] - 2026-05-06
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- `ga` (mdc v1.6 → v1.7): додано **універсальну** вимогу — кожен workflow у `.github/workflows/*.yml` обов'язково містить блок `concurrency` з `group: ${{ github.ref }}-${{ github.workflow }}` і `cancel-in-progress: true`. Без винятків — scheduled cleanup-воркфлоу, `pull_request: types: [closed]`, publish-воркфлоу теж. Канонічні приклади у правилі (`clean-ga-workflows.yml`, `clean-merged-branch.yml`, `git-ai.yml`) оновлено й тепер містять цей блок.
|
|
20
|
+
- `check-ga.mjs`: нова перевірка `verifyConcurrencyBlock` — запускається на кожному `*.yml` у `.github/workflows/` і структурно перевіряє рівно два поля (`concurrency.group` дорівнює канонічному рядку, `concurrency.cancel-in-progress === true`); відсутність блоку, інший `group` або `cancel-in-progress: false` — fail. Спільний `validateConcurrencyOnRoot` додано в усі канонічні структурні валідатори (clean-ga-workflows, clean-merged-branch, lint-ga, git-ai), щоб ці workflow перевірялися й через шаблонну, і через універсальну логіку.
|
|
21
|
+
|
|
22
|
+
## [1.8.182] - 2026-05-06
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- `js-run` (mdc v1.2 → v1.3): додано секцію **«depcheck у GitHub Actions з path-фільтром»** — якщо в `.github/workflows/*.yml` тригер `paths:` обмежено каталогом одного backend-пакета (наприклад `cron-jobs/refund-loyalty-points/**`), у job має бути крок `npx depcheck --ignores="graphql,bun"` з `working-directory`, що вказує на той самий каталог. Список `--ignores` обов'язково містить мінімум `graphql,bun` (peer-залежність GraphQL та рантайм Bun, які depcheck не розпізнає коректно), але може бути розширений значеннями через кому без пробілів. Не застосовується до глобальних workflow без `paths:` або з кореневими `**/*.js` патернами.
|
|
27
|
+
|
|
28
|
+
## [1.8.181] - 2026-05-06
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- `scripts/utils/find-package-json-paths.mjs`: винесено спільну `findAllPackageJsonPaths(repoRoot, ignorePaths)` з `check-js-bun-db.mjs` і `check-js-mssql.mjs`, щоб усунути jscpd-дублювання. Самі check-скрипти тепер імпортують її, як інші утиліти з `utils/`.
|
|
33
|
+
- `scripts/utils/walkDir.mjs` / `scripts/utils/load-cursor-config.mjs`: trimming trailing-slash переписано з регулярки `/\\/+$/` на `while (s.endsWith('/'))`, щоб уникнути попередження `sonarjs/slow-regex` (потенційний backtracking) — поведінка не змінилась.
|
|
34
|
+
- `scripts/check-k8s.mjs`: `failIfExplicitPatchTargetsHaveRedundantGroupVersion` рефакторено — логіка одного запису винесена в новий хелпер `describePatchTargetRedundancy`, основна функція тепер просто будує повідомлення з результату (зменшено sonarjs/cognitive-complexity 24→<15, поведінка не змінилась).
|
|
35
|
+
- `scripts/claude-stop-hook.mjs`: `readStdin` і `runStopHookCli` переписані з `new Promise(resolve => …)` на `events.once(stream, 'end' | 'exit')` — підказка `eslint-plugin-promise/avoid-new`, поведінка не змінилась.
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
- `scripts/utils/bun-sql-scan.mjs`: у JSDoc `findBunSqlUnsafeUseWithoutAllowMarkerInText` прибрано вкладений приклад із backslash-backtick (`sql\\`...\\${value}...\\``), який ламав парсер коментарів oxlint і призводив до false-positive `eslint-plugin-jsdoc(require-param)`/`(require-returns)` на функції з валідним JSDoc — текст переписано без екранованих backtick-ів.
|
|
40
|
+
- Десятки `eslint-plugin-jsdoc` правил у `npm/scripts/**` та `npm/tests/**`: додано відсутні описи `@param` / `@returns` (включно зі спільним `ignorePaths`-аргументом у нових сигнатурах walkDir-обгорток), прибрано неприпустимі дефолтні значення в JSDoc (`[name=...]`) — без зміни поведінки.
|
|
41
|
+
|
|
42
|
+
## [1.8.180] - 2026-05-05
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- `js-run` (mdc v1.1 → v1.2): додано секцію **«Область застосування»** — правило явно не застосовується до frontend-пакетів (маркер `vite` у `devDependencies`). У браузерному бандлі немає `node:process`, тому заміна `process.env.X` на `import { env } from 'node:process'` ламає рантайм (`TypeError: Cannot read properties of undefined (reading 'X')`); для frontend замість `process.env.NODE_ENV` — `import.meta.env.MODE` / `import.meta.env.PROD`, інші ENV — лише `import.meta.env.VITE_*`. Передумова — інцидент у abie/b2b `site/`, де LLM-агент за правилом замінив `process.env.NODE_ENV` у `src/main.js` і вибив прод-бандл.
|
|
47
|
+
- `check-js-run.mjs`: workspace-пакети з `vite` у `devDependencies` пропускаються — нова `packageJsonHasViteDevDependency(pkgJson)`, виклик одразу після `loadPackageJsonAndCheckBunyanDeps`. bunyan-залежність у `package.json` все одно перевіряється (бо це робиться до раннього виходу), але скан `process.env`, `#conn/*` і OTEL configmap для frontend-пакета не запускається. Тести: 2 нові кейси у `check-js-run-fixture.test.mjs` (vite-пакет з прямим `process.env` — pass; non-vite пакет з тим же кодом — fail).
|
|
48
|
+
|
|
7
49
|
## [1.8.179] - 2026-05-05
|
|
8
50
|
|
|
9
51
|
### Changed
|
|
@@ -48,9 +90,6 @@
|
|
|
48
90
|
### Added
|
|
49
91
|
|
|
50
92
|
- Нове правило `changelog` (`mdc/changelog.mdc` + `scripts/check-changelog.mjs`): для «звичайних» Bun-монорепо проєктів вимагає, щоб у кожному workspace, який змінився відносно базової гілки `dev`, у поточному PR було підвищено `version` у `<ws>/package.json` і додано запис `## [version] - YYYY-MM-DD` у `<ws>/CHANGELOG.md` (Keep a Changelog 1.1.0). Перевірка PR-scoped: на самій гілці `dev` пропускається; на feature-гілці bump і запис достатньо зробити **один раз — як суму по всьому PR**, без бамп-шуму в проміжних комітах. Воркспейс `npm/` пропускається — його CHANGELOG покриває окреме правило `npm-module`. У `auto-rules.md` / `auto-rules.mjs` `changelog` додано до автодетекту з умовою «у корені є `package.json`» і до `AUTO_RULE_ORDER` між `capacitor` і `docker`.
|
|
51
|
-
|
|
52
|
-
### Added
|
|
53
|
-
|
|
54
93
|
- `.n-cursor.json` поле `ignore` (`schemas/n-cursor.json`): тепер не лише сигнал для AI, а й керує обходом усіх `check-*.mjs` / `run-*.mjs` — перелічені каталоги повністю виключаються з `walkDir`, як `node_modules` чи `.git`. Дозволяє безпечно тримати vendored Helm-чарти, генеровані маніфести, legacy-дерева у репо без false-positive’ів від check-скриптів. Розширено опис у схемі (стандартні виключення додавати не треба) і README отримав секцію «Виключення цілих дерев».
|
|
55
94
|
- `scripts/utils/load-cursor-config.mjs`: нова утиліта `loadCursorIgnorePaths(root)` — читає поле `ignore` з `.n-cursor.json` і нормалізує до абсолютних posix-шляхів без trailing-slash; пропускає не-рядки та порожні елементи; повертає `[]`, якщо файлу/поля нема або JSON невалідний.
|
|
56
95
|
- `scripts/utils/walkDir.mjs`: третій аргумент `ignorePaths` (за замовчуванням `[]`) — каталоги, які пропускаються разом з усім вмістом. Збіг — за повним шляхом (точний або з префіксом `/`), а не за basename, тож `postgres-master-test/` не пропускається коли в ignore лише `postgres-master/`. Стандартні пропуски (`node_modules`, `.git`, `dist`, `coverage`, `.turbo`, `.next`) працюють як раніше.
|
|
@@ -147,8 +186,8 @@
|
|
|
147
186
|
|
|
148
187
|
### Changed
|
|
149
188
|
|
|
150
|
-
- `js-bun-db.mdc` (v1.4): `sql.unsafe(...)` тепер заборонено за замовчуванням — допустимо лише для підстановки назви таблиці/колонки чи dynamic SQL/DDL з code-controlled значенням; інакше переробляємо на tagged template `sql\`...${value}...\``. Кожен легітимний виклик має супроводжуватись
|
|
151
|
-
- `check-js-bun-db.mjs`: замість вузької перевірки `sql.unsafe
|
|
189
|
+
- `js-bun-db.mdc` (v1.4): `sql.unsafe(...)` тепер заборонено за замовчуванням — допустимо лише для підстановки назви таблиці/колонки чи dynamic SQL/DDL з code-controlled значенням; інакше переробляємо на tagged template `sql\`...${value}...\``. Кожен легітимний виклик має супроводжуватись маркером`// allow-unsafe: <причина>` на тому ж рядку або рядком вище.
|
|
190
|
+
- `check-js-bun-db.mjs`: замість вузької перевірки `sql.unsafe` із tagged-template і інтерполяцією тепер сканер `findBunSqlUnsafeUseWithoutAllowMarkerInText` падає на будь-якому `obj.unsafe(...)` без маркера-коментаря з непорожньою причиною (line- або block-коментар на тому ж рядку чи безпосередньо перед викликом).
|
|
152
191
|
- `ast-scan-utils.mjs`: додано `parseProgramAndCommentsOrNull` — окремий вхід для перевірок, яким потрібні коментарі поряд з AST.
|
|
153
192
|
|
|
154
193
|
## [1.8.159] - 2026-05-01
|
package/mdc/ga.mdc
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Правила форматів для .github/workflows
|
|
3
3
|
alwaysApply: true
|
|
4
|
-
version: '1.
|
|
4
|
+
version: '1.7'
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
У `.github/workflows/` лише **`.yml`**. Мають бути **`clean-ga-workflows.yml`**, **`clean-merged-branch.yml`**, **`lint-ga.yml`**, **`git-ai.yml`**. Якщо є **`apply-k8s.yml`** / **`apply-nats-consumer.yml`** — paths у тригері як у фрагментах.
|
|
8
8
|
|
|
9
|
+
**Кожен** workflow у `.github/workflows/*.yml` **обов'язково** містить блок `concurrency` з фіксованим `group` та `cancel-in-progress: true`:
|
|
10
|
+
|
|
11
|
+
```yaml
|
|
12
|
+
concurrency:
|
|
13
|
+
group: ${{ github.ref }}-${{ github.workflow }}
|
|
14
|
+
cancel-in-progress: true
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Без винятків — у scheduled cleanup-воркфлоу, у `pull_request: types: [closed]`, у publish-воркфлоу теж. Це уникає паралельних запусків того самого workflow на тій самій ref і скасовує попередні в чергу нових.
|
|
18
|
+
|
|
9
19
|
Повинен бути файл .github/workflows/clean-ga-workflows.yml, зі змістом:
|
|
10
20
|
|
|
11
21
|
```yaml
|
|
@@ -18,6 +28,10 @@ on:
|
|
|
18
28
|
# Allow workflow to be manually run from the GitHub UI
|
|
19
29
|
workflow_dispatch: {}
|
|
20
30
|
|
|
31
|
+
concurrency:
|
|
32
|
+
group: ${{ github.ref }}-${{ github.workflow }}
|
|
33
|
+
cancel-in-progress: true
|
|
34
|
+
|
|
21
35
|
jobs:
|
|
22
36
|
cleanup_old_workflows:
|
|
23
37
|
runs-on: ubuntu-latest
|
|
@@ -47,6 +61,10 @@ on:
|
|
|
47
61
|
# Allow workflow to be manually run from the GitHub UI
|
|
48
62
|
workflow_dispatch: {}
|
|
49
63
|
|
|
64
|
+
concurrency:
|
|
65
|
+
group: ${{ github.ref }}-${{ github.workflow }}
|
|
66
|
+
cancel-in-progress: true
|
|
67
|
+
|
|
50
68
|
jobs:
|
|
51
69
|
cleanup_old_branches:
|
|
52
70
|
runs-on: ubuntu-latest
|
|
@@ -119,6 +137,10 @@ on:
|
|
|
119
137
|
pull_request:
|
|
120
138
|
types: [closed]
|
|
121
139
|
|
|
140
|
+
concurrency:
|
|
141
|
+
group: ${{ github.ref }}-${{ github.workflow }}
|
|
142
|
+
cancel-in-progress: true
|
|
143
|
+
|
|
122
144
|
jobs:
|
|
123
145
|
git-ai:
|
|
124
146
|
if: github.event.pull_request.merged == true
|
package/mdc/js-run.mdc
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Це правила для backend проектів на JavaScript/Node.js, сюди входять і job і WEB сервери.
|
|
3
3
|
alwaysApply: true
|
|
4
|
-
version: '1.
|
|
4
|
+
version: '1.3'
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## Область застосування
|
|
8
|
+
|
|
9
|
+
Правило стосується **виключно backend Node.js workspace-пакетів** (jobs, GraphQL/HTTP-сервери, CLI). **Не застосовується** до frontend-пакетів, які бандляться в браузер: маркер — наявність `vite` у `devDependencies` пакета (`site/`, мобільні Capacitor-пакети, будь-яка Vue/Quasar SPA).
|
|
10
|
+
|
|
11
|
+
У браузерному середовищі:
|
|
12
|
+
|
|
13
|
+
- немає `node:process` — імпорт `import { env } from 'node:process'` resolve'иться у `undefined`, і `env.X` падає з `TypeError: Cannot read properties of undefined`;
|
|
14
|
+
- `process.env.X` у джерелах пакета відсутнє в рантаймі — Vite або взагалі не підставляє його, або підставляє лише `process.env.NODE_ENV`;
|
|
15
|
+
- усі змінні оточення для frontend задаються через `VITE_*` і доступні як `import.meta.env.VITE_X` (типобезпечно через `vite-check-env`); режим — `import.meta.env.MODE` / `import.meta.env.PROD`.
|
|
16
|
+
|
|
17
|
+
Тому **у frontend-пакетах не торкайся `process.env.*`** і **не додавай** `import { env } from 'node:process'`. Якщо натрапив на `process.env.NODE_ENV` у frontend-коді — заміна, якщо взагалі потрібна, лише на `import.meta.env.MODE`.
|
|
18
|
+
|
|
7
19
|
## Структура проекту
|
|
8
20
|
|
|
9
21
|
Рекомендується використовувати таку структуру проекту:
|
|
@@ -108,6 +120,8 @@ export const db = new SQL({ url: env.PG_CONN })
|
|
|
108
120
|
|
|
109
121
|
Прямий доступ до `process.env.X` у коді заборонений — його треба замінити на `env`:
|
|
110
122
|
|
|
123
|
+
> Стосується лише backend-пакетів (див. **Область застосування**). У frontend-пакетах (`vite` у `devDependencies`) — **не змінюй** `process.env.*` і **не додавай** імпорт `node:process`.
|
|
124
|
+
|
|
111
125
|
- **обов'язкова змінна** — `import { checkEnv, env } from '@nitra/check-env'` плюс `checkEnv(['X'])`
|
|
112
126
|
у тому ж файлі (приклад див. вище в розділі **CheckEnv**);
|
|
113
127
|
- **опційна змінна** — `import { env } from 'node:process'`:
|
|
@@ -122,6 +136,26 @@ console.log(env.OPTIONAL_ENV_VAR)
|
|
|
122
136
|
`// @nitra/cursor ignore-next-line checkEnv` безпосередньо перед використанням
|
|
123
137
|
(escape-hatch для legacy-коду, не для нових файлів).
|
|
124
138
|
|
|
139
|
+
## depcheck у GitHub Actions з path-фільтром
|
|
140
|
+
|
|
141
|
+
Якщо в `.github/workflows/*.yml` є тригер з `paths:`, який обмежує запуск workflow змінами в каталозі конкретного backend-пакета, в job цього workflow має бути крок `npx depcheck` з `working-directory`, який вказує на той самий каталог пакета. Це гарантує, що декларація залежностей у `package.json` пакета відповідає реальним імпортам — інакше можна випадково зламати білд після видалення «зайвої» залежності, яка насправді використовується через побічний імпорт.
|
|
142
|
+
|
|
143
|
+
Список `--ignores` **обов'язково** містить як мінімум `graphql,bun` (це ті, які `depcheck` не вміє коректно розпізнавати: `graphql` — peer-залежність, що часто використовується без прямого імпорту в коді; `bun` — рантайм, не npm-пакет). За потреби список можна розширити іншими модулями, специфічними для пакета — список значень розділяється комою без пробілів.
|
|
144
|
+
|
|
145
|
+
```yaml title="Приклад: workflow для cron-jobs/refund-loyalty-points"
|
|
146
|
+
on:
|
|
147
|
+
push:
|
|
148
|
+
paths:
|
|
149
|
+
- 'cron-jobs/refund-loyalty-points/**'
|
|
150
|
+
|
|
151
|
+
# …
|
|
152
|
+
|
|
153
|
+
- run: npx depcheck --ignores="graphql,bun"
|
|
154
|
+
working-directory: cron-jobs/refund-loyalty-points
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Правило не застосовується до workflow без `paths:` або з `paths:`, який не звужує тригер до одного backend-пакета (наприклад, кореневі `lint-*.yml` з глобальними `**/*.js`).
|
|
158
|
+
|
|
125
159
|
## Перевірка
|
|
126
160
|
|
|
127
161
|
`npx @nitra/cursor check js-run`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nitra/cursor",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.184",
|
|
4
4
|
"description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -44,11 +44,11 @@
|
|
|
44
44
|
"oxc-parser": "^0.128.0",
|
|
45
45
|
"yaml": "^2.8.3"
|
|
46
46
|
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@nitra/cursor": "^1.8.170"
|
|
49
|
+
},
|
|
47
50
|
"engines": {
|
|
48
51
|
"bun": ">=1.3",
|
|
49
52
|
"node": ">=25"
|
|
50
|
-
},
|
|
51
|
-
"devDependencies": {
|
|
52
|
-
"@nitra/cursor": "^1.8.170"
|
|
53
53
|
}
|
|
54
54
|
}
|
package/scripts/check-abie.mjs
CHANGED
|
@@ -384,6 +384,7 @@ export function ignoreBranchesIncludesRequired(ignoreBranches, required) {
|
|
|
384
384
|
/**
|
|
385
385
|
* Збирає абсолютні шляхи до **.yaml** / **.yml** під деревом, де є сегмент **k8s**.
|
|
386
386
|
* @param {string} root корінь репозиторію
|
|
387
|
+
* @param {string[]} [ignorePaths] абсолютні шляхи каталогів, повністю виключених з обходу
|
|
387
388
|
* @returns {Promise<string[]>} відсортовані шляхи
|
|
388
389
|
*/
|
|
389
390
|
async function findK8sYamlFiles(root, ignorePaths = []) {
|
|
@@ -41,7 +41,7 @@ const NPM_VIEW_TIMEOUT_MS = 10_000
|
|
|
41
41
|
/**
|
|
42
42
|
* Тихо запускає `git` і повертає stdout або `null` при будь-якій помилці.
|
|
43
43
|
* @param {string[]} args аргументи `git`
|
|
44
|
-
* @returns {Promise<string | null>}
|
|
44
|
+
* @returns {Promise<string | null>} stdout процесу або `null` при будь-якій помилці виконання
|
|
45
45
|
*/
|
|
46
46
|
async function gitOrNull(args) {
|
|
47
47
|
try {
|
|
@@ -54,7 +54,7 @@ async function gitOrNull(args) {
|
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
56
|
* Чи робочий каталог — git-репозиторій.
|
|
57
|
-
* @returns {Promise<boolean>}
|
|
57
|
+
* @returns {Promise<boolean>} `true`, якщо `git rev-parse --is-inside-work-tree` повернув `true`
|
|
58
58
|
*/
|
|
59
59
|
async function isInsideGitRepo() {
|
|
60
60
|
const out = await gitOrNull(['rev-parse', '--is-inside-work-tree'])
|
|
@@ -63,7 +63,7 @@ async function isInsideGitRepo() {
|
|
|
63
63
|
|
|
64
64
|
/**
|
|
65
65
|
* Назва поточної гілки (або `HEAD` для detached state).
|
|
66
|
-
* @returns {Promise<string | null>}
|
|
66
|
+
* @returns {Promise<string | null>} назва гілки чи `'HEAD'`, або `null` (поза git / помилка)
|
|
67
67
|
*/
|
|
68
68
|
async function currentBranchName() {
|
|
69
69
|
const out = await gitOrNull(['rev-parse', '--abbrev-ref', 'HEAD'])
|
|
@@ -73,7 +73,7 @@ async function currentBranchName() {
|
|
|
73
73
|
/**
|
|
74
74
|
* Знаходить ref для базової гілки. Перевага локальному `dev`, далі `origin/dev`. Повертає `null`,
|
|
75
75
|
* якщо жоден не існує.
|
|
76
|
-
* @returns {Promise<string | null>}
|
|
76
|
+
* @returns {Promise<string | null>} назва ref-а (`dev` чи `origin/dev`) або `null`, якщо жоден не знайдено
|
|
77
77
|
*/
|
|
78
78
|
async function resolveBaseRef() {
|
|
79
79
|
for (const ref of [BASE_BRANCH, `origin/${BASE_BRANCH}`]) {
|
|
@@ -88,8 +88,8 @@ async function resolveBaseRef() {
|
|
|
88
88
|
/**
|
|
89
89
|
* Точка розгалуження поточної гілки від `baseRef`. На feature-гілці = коли вона відгалузилась;
|
|
90
90
|
* на `main` після merge `dev → main` = поточний `dev`. Повертає `null`, якщо merge-base нема.
|
|
91
|
-
* @param {string} baseRef
|
|
92
|
-
* @returns {Promise<string | null>}
|
|
91
|
+
* @param {string} baseRef SHA або ref-name бази (зазвичай `dev` / `origin/dev`)
|
|
92
|
+
* @returns {Promise<string | null>} SHA точки розгалуження або `null`, якщо merge-base нема
|
|
93
93
|
*/
|
|
94
94
|
async function resolveMergeBase(baseRef) {
|
|
95
95
|
const out = await gitOrNull(['merge-base', baseRef, 'HEAD'])
|
|
@@ -104,9 +104,9 @@ async function resolveMergeBase(baseRef) {
|
|
|
104
104
|
* Для кореня `.` — це точка плюс magic-виключення кожного підворкспейсу через `:(exclude)<sub>/`,
|
|
105
105
|
* щоб зміни всередині sub-workspace не вважалися змінами кореня.
|
|
106
106
|
* Для звичайного воркспейсу — просто `<ws>/`.
|
|
107
|
-
* @param {string} ws
|
|
108
|
-
* @param {string[]} subWorkspaces
|
|
109
|
-
* @returns {string[]}
|
|
107
|
+
* @param {string} ws шлях воркспейсу (`'.'` для кореня, інакше — відносний шлях, як у `workspaces`)
|
|
108
|
+
* @param {string[]} subWorkspaces усі під-воркспейси (зокрема для `'.'` потрібно виключити їх)
|
|
109
|
+
* @returns {string[]} pathspec для git: масив, що передається після `--`
|
|
110
110
|
*/
|
|
111
111
|
function pathspecForWorkspace(ws, subWorkspaces) {
|
|
112
112
|
if (ws !== '.') return [`${ws}/`]
|
|
@@ -119,9 +119,9 @@ function pathspecForWorkspace(ws, subWorkspaces) {
|
|
|
119
119
|
* `git diff --quiet <baseRef> -- <pathspec>` ловить committed-зміни на цій гілці й незбережені
|
|
120
120
|
* правки tracked-файлів. Untracked-файли — `git ls-files --others --exclude-standard`.
|
|
121
121
|
* @param {string} baseRef SHA або ref-name (зокрема merge-base)
|
|
122
|
-
* @param {string} ws
|
|
123
|
-
* @param {string[]} subWorkspaces
|
|
124
|
-
* @returns {Promise<boolean>}
|
|
122
|
+
* @param {string} ws шлях воркспейсу (`'.'` для кореня)
|
|
123
|
+
* @param {string[]} subWorkspaces усі під-воркспейси для коректного формування pathspec кореня
|
|
124
|
+
* @returns {Promise<boolean>} `true`, якщо в межах воркспейсу є будь-які зміни (committed або untracked)
|
|
125
125
|
*/
|
|
126
126
|
async function workspaceHasChangesAgainstBase(baseRef, ws, subWorkspaces) {
|
|
127
127
|
const pathspec = pathspecForWorkspace(ws, subWorkspaces)
|
|
@@ -129,8 +129,7 @@ async function workspaceHasChangesAgainstBase(baseRef, ws, subWorkspaces) {
|
|
|
129
129
|
await execFileAsync('git', ['diff', '--quiet', baseRef, '--', ...pathspec])
|
|
130
130
|
} catch (error) {
|
|
131
131
|
const code = /** @type {{ code?: number }} */ (error).code
|
|
132
|
-
|
|
133
|
-
return false
|
|
132
|
+
return code === 1
|
|
134
133
|
}
|
|
135
134
|
const untracked = await gitOrNull(['ls-files', '--others', '--exclude-standard', '--', ...pathspec])
|
|
136
135
|
return typeof untracked === 'string' && untracked.trim().length > 0
|
|
@@ -138,9 +137,9 @@ async function workspaceHasChangesAgainstBase(baseRef, ws, subWorkspaces) {
|
|
|
138
137
|
|
|
139
138
|
/**
|
|
140
139
|
* Версія з `<ws>/package.json` на `baseRef` або `null`.
|
|
141
|
-
* @param {string} baseRef
|
|
142
|
-
* @param {string} ws
|
|
143
|
-
* @returns {Promise<string | null>}
|
|
140
|
+
* @param {string} baseRef SHA або ref-name (зазвичай merge-base) для `git show`
|
|
141
|
+
* @param {string} ws шлях воркспейсу (`'.'` для кореня)
|
|
142
|
+
* @returns {Promise<string | null>} значення поля `version` або `null`, якщо файла нема / JSON некоректний
|
|
144
143
|
*/
|
|
145
144
|
async function readBaseVersion(baseRef, ws) {
|
|
146
145
|
const wsPath = ws === '.' ? 'package.json' : `${ws}/package.json`
|
|
@@ -156,9 +155,9 @@ async function readBaseVersion(baseRef, ws) {
|
|
|
156
155
|
|
|
157
156
|
/**
|
|
158
157
|
* Чи містить текст `CHANGELOG.md` запис `## [version]` (з опційним `- YYYY-MM-DD`).
|
|
159
|
-
* @param {string} text
|
|
160
|
-
* @param {string} version
|
|
161
|
-
* @returns {boolean}
|
|
158
|
+
* @param {string} text вміст CHANGELOG.md
|
|
159
|
+
* @param {string} version версія, яку шукаємо у форматі Keep a Changelog
|
|
160
|
+
* @returns {boolean} `true`, якщо запис для `version` знайдено
|
|
162
161
|
*/
|
|
163
162
|
function changelogHasVersionEntry(text, version) {
|
|
164
163
|
const escaped = version.replaceAll(/[.+*?^$()[\]{}|\\]/g, String.raw`\$&`)
|
|
@@ -168,8 +167,8 @@ function changelogHasVersionEntry(text, version) {
|
|
|
168
167
|
|
|
169
168
|
/**
|
|
170
169
|
* Зчитує `<ws>/package.json`. `null`, якщо файл відсутній або JSON некоректний.
|
|
171
|
-
* @param {string} ws
|
|
172
|
-
* @returns {Promise<Record<string, unknown> | null>}
|
|
170
|
+
* @param {string} ws шлях воркспейсу (`'.'` для кореня)
|
|
171
|
+
* @returns {Promise<Record<string, unknown> | null>} розпарсений `package.json` або `null`
|
|
173
172
|
*/
|
|
174
173
|
async function readPackageJsonOrNull(ws) {
|
|
175
174
|
const path = join(ws, 'package.json')
|
|
@@ -186,8 +185,8 @@ async function readPackageJsonOrNull(ws) {
|
|
|
186
185
|
|
|
187
186
|
/**
|
|
188
187
|
* Воркспейс публікується в npm: має непорожній `name`, не `private: true`, і має масив `files`.
|
|
189
|
-
* @param {Record<string, unknown> | null} pkg
|
|
190
|
-
* @returns {boolean}
|
|
188
|
+
* @param {Record<string, unknown> | null} pkg розпарсений `package.json` (або `null`)
|
|
189
|
+
* @returns {boolean} `true`, якщо пакет придатний для публікації в npm
|
|
191
190
|
*/
|
|
192
191
|
function isNpmPublishable(pkg) {
|
|
193
192
|
if (!pkg) return false
|
|
@@ -199,8 +198,8 @@ function isNpmPublishable(pkg) {
|
|
|
199
198
|
/**
|
|
200
199
|
* Опублікована версія пакета в npm-реєстрі. `null` — пакет не знайдено / нема мережі / помилка.
|
|
201
200
|
* Дефолтна імплементація — `npm view <name> version` із таймаутом, щоб не блокуватись офлайн.
|
|
202
|
-
* @param {string} name
|
|
203
|
-
* @returns {Promise<string | null>}
|
|
201
|
+
* @param {string} name повна назва пакета (включно зі скоупом)
|
|
202
|
+
* @returns {Promise<string | null>} опублікована версія або `null` (нема пакета / офлайн)
|
|
204
203
|
*/
|
|
205
204
|
async function defaultGetPublishedVersion(name) {
|
|
206
205
|
try {
|
|
@@ -214,10 +213,10 @@ async function defaultGetPublishedVersion(name) {
|
|
|
214
213
|
|
|
215
214
|
/**
|
|
216
215
|
* Перевіряє масив `files` у `<ws>/package.json`: якщо оголошено — має містити `"CHANGELOG.md"`.
|
|
217
|
-
* @param {Record<string, unknown> | null} pkg
|
|
218
|
-
* @param {string} ws
|
|
219
|
-
* @param {(msg: string) => void} pass
|
|
220
|
-
* @param {(msg: string) => void} fail
|
|
216
|
+
* @param {Record<string, unknown> | null} pkg розпарсений `package.json` воркспейсу
|
|
217
|
+
* @param {string} ws шлях воркспейсу (`'.'` для кореня)
|
|
218
|
+
* @param {(msg: string) => void} pass callback при успішній перевірці
|
|
219
|
+
* @param {(msg: string) => void} fail callback при помилці
|
|
221
220
|
*/
|
|
222
221
|
function checkFilesArrayContainsChangelog(pkg, ws, pass, fail) {
|
|
223
222
|
if (!pkg || !Array.isArray(pkg.files)) return
|
|
@@ -231,10 +230,10 @@ function checkFilesArrayContainsChangelog(pkg, ws, pass, fail) {
|
|
|
231
230
|
|
|
232
231
|
/**
|
|
233
232
|
* Перевіряє наявність запису у `<ws>/CHANGELOG.md` для версії `version`.
|
|
234
|
-
* @param {string} ws
|
|
235
|
-
* @param {string} version
|
|
236
|
-
* @param {(msg: string) => void} pass
|
|
237
|
-
* @param {(msg: string) => void} fail
|
|
233
|
+
* @param {string} ws шлях воркспейсу (`'.'` для кореня)
|
|
234
|
+
* @param {string} version версія, для якої очікується запис
|
|
235
|
+
* @param {(msg: string) => void} pass callback при успішній перевірці
|
|
236
|
+
* @param {(msg: string) => void} fail callback при помилці
|
|
238
237
|
* @returns {Promise<boolean>} `false`, якщо файл відсутній або немає запису
|
|
239
238
|
*/
|
|
240
239
|
async function verifyChangelogEntry(ws, version, pass, fail) {
|
|
@@ -257,11 +256,11 @@ async function verifyChangelogEntry(ws, version, pass, fail) {
|
|
|
257
256
|
* npm-published режим: порівнює локальну `version` з опублікованою в реєстрі. Якщо вони
|
|
258
257
|
* відрізняються — вимагає запис у CHANGELOG і `"CHANGELOG.md"` у `files`. Якщо реєстр недосяжний,
|
|
259
258
|
* правило fail-safe пасує (щоб офлайн-розробка не блокувалась).
|
|
260
|
-
* @param {string} ws
|
|
261
|
-
* @param {Record<string, unknown>} pkg
|
|
262
|
-
* @param {(name: string) => Promise<string | null>} getPublishedVersion
|
|
263
|
-
* @param {(msg: string) => void} pass
|
|
264
|
-
* @param {(msg: string) => void} fail
|
|
259
|
+
* @param {string} ws шлях воркспейсу (`'.'` для кореня)
|
|
260
|
+
* @param {Record<string, unknown>} pkg розпарсений `package.json` воркспейсу
|
|
261
|
+
* @param {(name: string) => Promise<string | null>} getPublishedVersion стаб/реальна функція отримання опублікованої версії
|
|
262
|
+
* @param {(msg: string) => void} pass callback при успішній перевірці
|
|
263
|
+
* @param {(msg: string) => void} fail callback при помилці
|
|
265
264
|
*/
|
|
266
265
|
async function checkPublishedWorkspace(ws, pkg, getPublishedVersion, pass, fail) {
|
|
267
266
|
const label = ws === '.' ? '<root>' : ws
|
|
@@ -289,10 +288,10 @@ async function checkPublishedWorkspace(ws, pkg, getPublishedVersion, pass, fail)
|
|
|
289
288
|
* local-only режим: PR-scoped перевірка проти `dev` через `git merge-base`. Викликається лише
|
|
290
289
|
* для воркспейсів, де є реальні зміни щодо merge-base.
|
|
291
290
|
* @param {string} mergeBase SHA точки розгалуження
|
|
292
|
-
* @param {string} ws
|
|
293
|
-
* @param {Record<string, unknown> | null} pkg
|
|
294
|
-
* @param {(msg: string) => void} pass
|
|
295
|
-
* @param {(msg: string) => void} fail
|
|
291
|
+
* @param {string} ws шлях воркспейсу (`'.'` для кореня)
|
|
292
|
+
* @param {Record<string, unknown> | null} pkg розпарсений `package.json` воркспейсу (або `null`)
|
|
293
|
+
* @param {(msg: string) => void} pass callback при успішній перевірці
|
|
294
|
+
* @param {(msg: string) => void} fail callback при помилці
|
|
296
295
|
*/
|
|
297
296
|
async function checkLocalOnlyChangedWorkspace(mergeBase, ws, pkg, pass, fail) {
|
|
298
297
|
const label = ws === '.' ? '<root>' : ws
|
|
@@ -315,12 +314,12 @@ async function checkLocalOnlyChangedWorkspace(mergeBase, ws, pkg, pass, fail) {
|
|
|
315
314
|
|
|
316
315
|
/**
|
|
317
316
|
* Виконує local-only перевірку для всіх workspace-ів, у яких немає npm-published режиму.
|
|
318
|
-
* @param {string[]} localOnlyWorkspaces
|
|
319
|
-
* @param {Map<string, Record<string, unknown> | null>} pkgByWs
|
|
320
|
-
* @param {string[]} subWorkspaces
|
|
321
|
-
* @param {(msg: string) => void} pass
|
|
322
|
-
* @param {(msg: string) => void} fail
|
|
323
|
-
* @returns {Promise<void>}
|
|
317
|
+
* @param {string[]} localOnlyWorkspaces список шляхів local-only воркспейсів
|
|
318
|
+
* @param {Map<string, Record<string, unknown> | null>} pkgByWs мапа: шлях воркспейсу → розпарсений `package.json` (або `null`)
|
|
319
|
+
* @param {string[]} subWorkspaces усі під-воркспейси (для коректного pathspec кореня)
|
|
320
|
+
* @param {(msg: string) => void} pass callback при успішній перевірці
|
|
321
|
+
* @param {(msg: string) => void} fail callback при помилці
|
|
322
|
+
* @returns {Promise<void>} резолвиться по завершенню перевірок усіх local-only воркспейсів
|
|
324
323
|
*/
|
|
325
324
|
async function runLocalOnlyChecks(localOnlyWorkspaces, pkgByWs, subWorkspaces, pass, fail) {
|
|
326
325
|
if (localOnlyWorkspaces.length === 0) return
|
|
@@ -358,7 +357,7 @@ async function runLocalOnlyChecks(localOnlyWorkspaces, pkgByWs, subWorkspaces, p
|
|
|
358
357
|
|
|
359
358
|
/**
|
|
360
359
|
* Перевіряє відповідність проєкту правилу changelog.mdc.
|
|
361
|
-
* @param {object} [opts]
|
|
360
|
+
* @param {object} [opts] опції перевірки
|
|
362
361
|
* @param {(name: string) => Promise<string | null>} [opts.getPublishedVersion] перевизначення для тестів
|
|
363
362
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
364
363
|
*/
|
package/scripts/check-docker.mjs
CHANGED
|
@@ -66,7 +66,7 @@ export function isDockerfileName(name) {
|
|
|
66
66
|
/**
|
|
67
67
|
* Збирає абсолютні шляхи до Dockerfile / Containerfile від кореня cwd.
|
|
68
68
|
* @param {string} root корінь репозиторію
|
|
69
|
-
* @param {string[]} [ignorePaths
|
|
69
|
+
* @param {string[]} [ignorePaths] шляхи каталогів, повністю виключених з обходу
|
|
70
70
|
* @returns {Promise<string[]>} відсортовані абсолютні шляхи
|
|
71
71
|
*/
|
|
72
72
|
export async function findDockerfilePaths(root, ignorePaths = []) {
|
package/scripts/check-ga.mjs
CHANGED
|
@@ -43,6 +43,8 @@ const MEGALINTER_USE_PATTERNS = [/oxsecurity\/megalinter-action/i, /megalinter\/
|
|
|
43
43
|
/** Типові конфіги MegaLinter у корені репо */
|
|
44
44
|
const MEGALINTER_CONFIG_NAMES = ['.mega-linter.yml', '.megalinter.yaml', '.mega-linter.yaml']
|
|
45
45
|
|
|
46
|
+
const N_CURSOR_LINT_GA_RE = /\bn-cursor\s+lint-ga\b/
|
|
47
|
+
|
|
46
48
|
/** Локальні composite setup-bun-deps (ga.mdc). */
|
|
47
49
|
const SETUP_BUN_PATTERNS = ['./.github/actions/setup-bun-deps', './npm/github-actions/setup-bun-deps']
|
|
48
50
|
|
|
@@ -56,6 +58,9 @@ const FORBIDDEN_BUN_PATTERNS = [
|
|
|
56
58
|
/** Обовʼязкові workflow-файли (ga.mdc). */
|
|
57
59
|
const REQUIRED_WORKFLOWS = ['clean-ga-workflows.yml', 'clean-merged-branch.yml', 'lint-ga.yml', 'git-ai.yml']
|
|
58
60
|
|
|
61
|
+
/** Канонічне значення `concurrency.group` (ga.mdc). Збирається з фрагментів, щоб не плодити expression-токени в коді. */
|
|
62
|
+
const EXPECTED_CONCURRENCY_GROUP = ['$', '{{ github.ref }}-$', '{{ github.workflow }}'].join('')
|
|
63
|
+
|
|
59
64
|
/**
|
|
60
65
|
* Повертає true, якщо glob у GitHub Actions `on.*.paths` матчитсья хоча б на один tracked файл у репозиторії.
|
|
61
66
|
*
|
|
@@ -229,6 +234,8 @@ function validateCleanGaWorkflows(root, passFn, failFn) {
|
|
|
229
234
|
passFn('clean-ga-workflows.yml: workflow_dispatch OK')
|
|
230
235
|
}
|
|
231
236
|
|
|
237
|
+
validateConcurrencyOnRoot('clean-ga-workflows.yml', root, passFn, failFn)
|
|
238
|
+
|
|
232
239
|
const jobs = getObjKey(root, 'jobs')
|
|
233
240
|
const job = getObjKey(jobs, 'cleanup_old_workflows')
|
|
234
241
|
if (!job) {
|
|
@@ -342,6 +349,8 @@ function validateCleanMergedBranch(root, passFn, failFn) {
|
|
|
342
349
|
failFn('clean-merged-branch.yml: має бути workflow_dispatch: {} (ga.mdc)')
|
|
343
350
|
}
|
|
344
351
|
|
|
352
|
+
validateConcurrencyOnRoot('clean-merged-branch.yml', root, passFn, failFn)
|
|
353
|
+
|
|
345
354
|
const jobs = getObjKey(root, 'jobs')
|
|
346
355
|
const job = getObjKey(jobs, 'cleanup_old_branches')
|
|
347
356
|
if (!job) {
|
|
@@ -419,10 +428,7 @@ function validateLintGaWorkflowStructure(root, passFn, failFn) {
|
|
|
419
428
|
|
|
420
429
|
validateLintGaOnTriggers(root.on, failFn)
|
|
421
430
|
|
|
422
|
-
|
|
423
|
-
if (getObjKey(conc, 'cancel-in-progress') !== true) {
|
|
424
|
-
failFn('lint-ga.yml: concurrency.cancel-in-progress має бути true (ga.mdc)')
|
|
425
|
-
}
|
|
431
|
+
validateConcurrencyOnRoot('lint-ga.yml', root, passFn, failFn)
|
|
426
432
|
|
|
427
433
|
const jobs = getObjKey(root, 'jobs')
|
|
428
434
|
const job = getObjKey(jobs, 'lint-ga')
|
|
@@ -488,6 +494,8 @@ function validateGitAiWorkflowStructure(root, passFn, failFn) {
|
|
|
488
494
|
failFn('git-ai.yml: on.pull_request.types має містити closed (ga.mdc)')
|
|
489
495
|
}
|
|
490
496
|
|
|
497
|
+
validateConcurrencyOnRoot('git-ai.yml', root, passFn, failFn)
|
|
498
|
+
|
|
491
499
|
const jobs = getObjKey(root, 'jobs')
|
|
492
500
|
const job = getObjKey(jobs, 'git-ai')
|
|
493
501
|
if (!job) {
|
|
@@ -516,6 +524,60 @@ function validateGitAiWorkflowStructure(root, passFn, failFn) {
|
|
|
516
524
|
}
|
|
517
525
|
}
|
|
518
526
|
|
|
527
|
+
/**
|
|
528
|
+
* Перевіряє блок `concurrency` на вже розпарсеному корені workflow (ga.mdc).
|
|
529
|
+
*
|
|
530
|
+
* Використовується в канонічних структурних валідаторах (clean-ga-workflows, clean-merged-branch,
|
|
531
|
+
* lint-ga, git-ai), де root уже отримано через `parseWorkflowYaml`. Логіка ідентична
|
|
532
|
+
* `verifyConcurrencyBlock`, але без повторного парсингу.
|
|
533
|
+
* @param {string} relPath шлях для повідомлень
|
|
534
|
+
* @param {Record<string, unknown>} root parsed YAML workflow
|
|
535
|
+
* @param {(msg: string) => void} passFn pass
|
|
536
|
+
* @param {(msg: string) => void} failFn fail
|
|
537
|
+
* @returns {void}
|
|
538
|
+
*/
|
|
539
|
+
function validateConcurrencyOnRoot(relPath, root, passFn, failFn) {
|
|
540
|
+
const conc = getObjKey(root, 'concurrency')
|
|
541
|
+
if (!conc || typeof conc !== 'object') {
|
|
542
|
+
failFn(
|
|
543
|
+
`${relPath}: відсутня секція concurrency — додай concurrency.group: ${EXPECTED_CONCURRENCY_GROUP} і cancel-in-progress: true (ga.mdc)`
|
|
544
|
+
)
|
|
545
|
+
return
|
|
546
|
+
}
|
|
547
|
+
const group = getObjKey(conc, 'group')
|
|
548
|
+
const cancel = getObjKey(conc, 'cancel-in-progress')
|
|
549
|
+
if (group !== EXPECTED_CONCURRENCY_GROUP) {
|
|
550
|
+
failFn(`${relPath}: concurrency.group має бути ${EXPECTED_CONCURRENCY_GROUP} (ga.mdc)`)
|
|
551
|
+
return
|
|
552
|
+
}
|
|
553
|
+
if (cancel !== true) {
|
|
554
|
+
failFn(`${relPath}: concurrency.cancel-in-progress має бути true (ga.mdc)`)
|
|
555
|
+
return
|
|
556
|
+
}
|
|
557
|
+
passFn(`${relPath}: concurrency блок OK`)
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Перевіряє, що workflow містить блок `concurrency` з канонічними `group` і `cancel-in-progress: true` (ga.mdc).
|
|
562
|
+
*
|
|
563
|
+
* Без винятків — застосовується до всіх workflow у `.github/workflows/*.yml`, включно з scheduled cleanup,
|
|
564
|
+
* `pull_request: types: [closed]` та publish-воркфлоу. Делегує логіку `validateConcurrencyOnRoot`,
|
|
565
|
+
* додаючи лише крок парсингу YAML; якщо парсинг провалився — мовчки виходить (синтаксичні проблеми
|
|
566
|
+
* ловлять інші перевірки).
|
|
567
|
+
* @param {string} relPath шлях для повідомлень
|
|
568
|
+
* @param {string} content вміст YAML
|
|
569
|
+
* @param {(msg: string) => void} failFn реєструє порушення (exit 1)
|
|
570
|
+
* @param {(msg: string) => void} passFn реєструє успішну перевірку
|
|
571
|
+
* @returns {void}
|
|
572
|
+
*/
|
|
573
|
+
function verifyConcurrencyBlock(relPath, content, failFn, passFn) {
|
|
574
|
+
const root = parseWorkflowYaml(content)
|
|
575
|
+
if (!root) {
|
|
576
|
+
return
|
|
577
|
+
}
|
|
578
|
+
validateConcurrencyOnRoot(relPath, root, passFn, failFn)
|
|
579
|
+
}
|
|
580
|
+
|
|
519
581
|
/**
|
|
520
582
|
* Якщо workflow викликає локальний setup-bun-deps, раніше у файлі має бути `actions/checkout@v…` (ga.mdc).
|
|
521
583
|
* Fallback: сирий текст, якщо YAML не вдається розібрати.
|
|
@@ -743,12 +805,10 @@ async function checkLintGaScript(passFn, failFn) {
|
|
|
743
805
|
// на shellcheck + послідовно `bunx github-actionlint` і `uvx zizmor --offline --collect=workflows .`.
|
|
744
806
|
// Виклик через bin-ім’я `n-cursor`, а не `npx --no @nitra/cursor`, бо `bun run` транслює `npx` у `bun x`,
|
|
745
807
|
// а `bun x @nitra/cursor` для скоупованого пакету з одним bin-ім’ям повертає 0 без виконання.
|
|
746
|
-
if (
|
|
808
|
+
if (N_CURSOR_LINT_GA_RE.test(lg)) {
|
|
747
809
|
passFn('lint-ga делегує CLI n-cursor lint-ga (preflight shellcheck + actionlint + zizmor)')
|
|
748
810
|
} else {
|
|
749
|
-
failFn(
|
|
750
|
-
'lint-ga має бути "n-cursor lint-ga" — CLI робить preflight shellcheck перед actionlint/zizmor (ga.mdc)'
|
|
751
|
-
)
|
|
811
|
+
failFn('lint-ga має бути "n-cursor lint-ga" — CLI робить preflight shellcheck перед actionlint/zizmor (ga.mdc)')
|
|
752
812
|
}
|
|
753
813
|
}
|
|
754
814
|
|
|
@@ -994,6 +1054,7 @@ export async function check() {
|
|
|
994
1054
|
verifyCheckoutBeforeLocalSetupBunDeps(`${wfDir}/${f}`, content, fail, pass)
|
|
995
1055
|
verifyNoDirectBunOrCache(`${wfDir}/${f}`, content, fail, pass)
|
|
996
1056
|
verifyNoRunShellLineContinuationBackslash(`${wfDir}/${f}`, content, fail, pass)
|
|
1057
|
+
verifyConcurrencyBlock(`${wfDir}/${f}`, content, fail, pass)
|
|
997
1058
|
const parsed = parseWorkflowYaml(content)
|
|
998
1059
|
if (parsed) {
|
|
999
1060
|
verifyWorkflowEventPathsGlobsExist(`${wfDir}/${f}`, parsed, pass, fail)
|
|
@@ -32,6 +32,7 @@ export const REQUIRED_DUMP_SCHEMA_SCRIPT =
|
|
|
32
32
|
/**
|
|
33
33
|
* Збирає абсолютні шляхи source-файлів, які підлягають скануванню на gql templates.
|
|
34
34
|
* @param {string} root абсолютний шлях кореня
|
|
35
|
+
* @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
|
|
35
36
|
* @returns {Promise<string[]>} список кандидатів
|
|
36
37
|
*/
|
|
37
38
|
async function collectScanCandidates(root, ignorePaths) {
|
package/scripts/check-hasura.mjs
CHANGED
|
@@ -102,6 +102,7 @@ export function isEnvFile(relPath) {
|
|
|
102
102
|
/**
|
|
103
103
|
* Збирає всі `*.env` файли в дереві, окрім службових каталогів.
|
|
104
104
|
* @param {string} root абсолютний шлях кореня
|
|
105
|
+
* @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
|
|
105
106
|
* @returns {Promise<string[]>} відсортовані posix-шляхи відносно кореня
|
|
106
107
|
*/
|
|
107
108
|
async function collectEnvFiles(root, ignorePaths) {
|