@nitra/cursor 3.21.1 → 3.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.pi-template/extensions/n-cursor-adr/docs/index.md +181 -0
- package/CHANGELOG.md +37 -3
- package/bin/docs/n-cursor.md +636 -0
- package/bin/docs/rename-yaml-extensions.md +207 -0
- package/bin/n-cursor.js +30 -3
- package/package.json +1 -1
- package/rules/abie/docs/fix.md +18 -0
- package/rules/abie/js/docs/applies.md +26 -0
- package/rules/abie/js/docs/env_dns.md +32 -0
- package/rules/abie/js/docs/firebase_hosting.md +23 -0
- package/rules/abie/js/docs/hc_pairing.md +35 -0
- package/rules/abie/js/docs/ua_http_route.md +28 -0
- package/rules/abie/js/docs/ua_node_selector.md +28 -0
- package/rules/abie/lib/docs/enabled.md +29 -0
- package/rules/abie/lib/docs/env-dns.md +35 -0
- package/rules/abie/lib/docs/hc-yaml.md +33 -0
- package/rules/abie/lib/docs/http-route.md +44 -0
- package/rules/abie/lib/docs/k8s-tree.md +40 -0
- package/rules/abie/lib/docs/kustomization-patches.md +47 -0
- package/rules/abie/lib/docs/overlay-paths.md +38 -0
- package/rules/abie/lib/docs/yaml.md +29 -0
- package/rules/adr/docs/fix.md +148 -0
- package/rules/adr/js/docs/hooks.md +259 -0
- package/rules/bun/docs/fix.md +156 -0
- package/rules/bun/js/docs/layout.md +393 -0
- package/rules/capacitor/docs/fix.md +121 -0
- package/rules/capacitor/js/docs/platforms.md +295 -0
- package/rules/changelog/changelog.mdc +2 -2
- package/rules/changelog/docs/fix.md +174 -0
- package/rules/changelog/js/consistency.mjs +114 -13
- package/rules/changelog/js/docs/consistency.md +387 -0
- package/rules/changelog/lib/docs/package-manifest.md +210 -0
- package/rules/ci4/docs/fix.md +179 -0
- package/rules/ci4/js/docs/marksman_config.md +128 -0
- package/rules/docker/docker.mdc +8 -3
- package/rules/docker/docs/fix.md +171 -0
- package/rules/docker/js/docs/lint.md +258 -0
- package/rules/docker/lib/docs/docker-hadolint.md +184 -0
- package/rules/docker/lib/docs/docker-mirror.md +247 -0
- package/rules/docker/lib/docs/docker-native-addon.md +170 -0
- package/rules/docker/lib/docs/docker-nginx-user.md +219 -0
- package/rules/docker/lint/docs/lint.md +193 -0
- package/rules/efes/docs/fix.md +203 -0
- package/rules/feedback/docs/fix.md +140 -0
- package/rules/flow/docs/fix.md +152 -0
- package/rules/ga/docs/fix.md +158 -0
- package/rules/ga/js/docs/lint.md +100 -0
- package/rules/ga/js/docs/workflows.md +217 -0
- package/rules/ga/lint/docs/lint.md +209 -0
- package/rules/ga/policy/clean_merged_branch/clean_merged_branch.rego +11 -2
- package/rules/ga/policy/clean_merged_branch/template/clean-merged-branch.yml.snippet.yml +3 -4
- package/rules/graphql/docs/fix.md +126 -0
- package/rules/graphql/js/docs/tooling.md +264 -0
- package/rules/graphql/lib/docs/graphql-gql-scan.md +219 -0
- package/rules/hasura/docs/fix.md +120 -0
- package/rules/hasura/hasura.mdc +14 -0
- package/rules/hasura/js/docs/internal_urls.md +326 -0
- package/rules/image-avif/docs/fix.md +132 -0
- package/rules/image-avif/js/docs/avif_generation.md +241 -0
- package/rules/image-compress/docs/fix.md +150 -0
- package/rules/image-compress/js/docs/package_setup.md +191 -0
- package/rules/js-bun-db/docs/fix.md +148 -0
- package/rules/js-bun-db/js/docs/safety.md +231 -0
- package/rules/js-bun-db/js-bun-db.mdc +42 -13
- package/rules/js-bun-db/lib/docs/bun-sql-scan.md +347 -0
- package/rules/js-bun-redis/docs/fix.md +123 -0
- package/rules/js-bun-redis/js/docs/imports.md +176 -0
- package/rules/js-bun-redis/lib/docs/redis-imports.md +223 -0
- package/rules/js-lint/docs/fix.md +117 -0
- package/rules/js-lint/js/docs/lint.md +250 -0
- package/rules/js-lint/js/docs/tooling.md +348 -0
- package/rules/js-lint/js/docs/utils_imports.md +207 -0
- package/rules/js-lint/js/lint-findings.mjs +110 -0
- package/rules/js-lint/js/lint.mjs +86 -15
- package/rules/js-lint-ci/docs/fix.md +154 -0
- package/rules/js-lint-ci/js/docs/lint.md +144 -0
- package/rules/js-mssql/docs/fix.md +128 -0
- package/rules/js-mssql/js/docs/deps.md +263 -0
- package/rules/js-mssql/lib/docs/mssql-pool-scan.md +367 -0
- package/rules/js-run/docs/fix.md +144 -0
- package/rules/js-run/js/docs/runtime.md +388 -0
- package/rules/js-run/lib/docs/bunyan-imports.md +117 -0
- package/rules/js-run/lib/docs/check-env-scan.md +433 -0
- package/rules/js-run/lib/docs/conn-file-rules.md +300 -0
- package/rules/js-run/lib/docs/conn-imports-scan.md +204 -0
- package/rules/js-run/lib/docs/promise-settimeout-scan.md +326 -0
- package/rules/k8s/docs/fix.md +129 -0
- package/rules/k8s/js/docs/manifests.md +344 -0
- package/rules/k8s/js/manifests.mjs +6 -2
- package/rules/k8s/k8s.mdc +4 -2
- package/rules/k8s/lint/docs/lint.md +411 -0
- package/rules/k8s/policy/network_policy/template/deployment.snippet.yaml +2 -0
- package/rules/k8s/policy/network_policy/template/stateful-set.snippet.yaml +2 -0
- package/rules/nginx-default-tpl/docs/fix.md +124 -0
- package/rules/nginx-default-tpl/js/docs/template.md +378 -0
- package/rules/npm-module/docs/fix.md +98 -0
- package/rules/npm-module/js/docs/package_structure.md +274 -0
- package/rules/npm-module/js/docs/rule_meta.md +137 -0
- package/rules/npm-module/js/docs/skill_meta.md +190 -0
- package/rules/php/docs/fix.md +107 -0
- package/rules/php/js/docs/tooling.md +152 -0
- package/rules/php/lint/docs/lint.md +215 -0
- package/rules/python/docs/fix.md +163 -0
- package/rules/python/js/docs/applies.md +108 -0
- package/rules/python/js/docs/tooling.md +153 -0
- package/rules/python/lint/docs/lint.md +322 -0
- package/rules/rego/docs/fix.md +121 -0
- package/rules/rego/js/docs/applies.md +174 -0
- package/rules/rego/js/docs/lint.md +118 -0
- package/rules/rego/lint/docs/lint.md +204 -0
- package/rules/release/docs/change.md +185 -0
- package/rules/release/docs/fix.md +119 -0
- package/rules/release/docs/release.md +222 -0
- package/rules/release/lib/docs/aggregate.md +246 -0
- package/rules/release/lib/docs/change-file.md +200 -0
- package/rules/release/lib/docs/fallback.md +203 -0
- package/rules/rust/docs/fix.md +129 -0
- package/rules/rust/js/docs/applies.md +140 -0
- package/rules/rust/lib/docs/has-cargo-toml.md +130 -0
- package/rules/security/docs/fix.md +86 -0
- package/rules/security/js/docs/lint.md +171 -0
- package/rules/security/js/docs/sample_secret.md +190 -0
- package/rules/security/js/docs/trufflehog.md +137 -0
- package/rules/security/js/lint.mjs +9 -1
- package/rules/style-lint/docs/fix.md +155 -0
- package/rules/style-lint/js/docs/lint.md +184 -0
- package/rules/style-lint/js/docs/tooling.md +194 -0
- package/rules/tauri/docs/fix.md +158 -0
- package/rules/tauri/js/docs/cargo_mutants_config.md +168 -0
- package/rules/tauri/js/docs/tooling.md +228 -0
- package/rules/test/coverage/coverage.mjs +15 -3
- package/rules/test/docs/fix.md +132 -0
- package/rules/test/js/data/stryker_config/docs/stryker-vue-macros-ignorer.md +138 -0
- package/rules/test/js/data/stryker_config/docs/stryker.config.baseline.md +134 -0
- package/rules/test/js/data/stryker_config/docs/stryker.config.vue.baseline.md +160 -0
- package/rules/test/js/data/vitest_config/docs/vitest.config.baseline.md +195 -0
- package/rules/test/js/docs/cargo_mutants_config.md +173 -0
- package/rules/test/js/docs/location.md +136 -0
- package/rules/test/js/docs/no-process-chdir.md +160 -0
- package/rules/test/js/docs/no-relative-fs-path.md +271 -0
- package/rules/test/js/docs/stryker_config.md +152 -0
- package/rules/test/js/docs/vitest-config-pool-forks.md +174 -0
- package/rules/text/docs/fix.md +118 -0
- package/rules/text/js/docs/forbidden-prettier.md +143 -0
- package/rules/text/js/docs/formatting.md +256 -0
- package/rules/text/js/docs/lint.md +122 -0
- package/rules/text/lint/docs/lint.md +220 -0
- package/rules/text/lint/docs/run-dotenv-linter.md +157 -0
- package/rules/text/lint/docs/run-shellcheck.md +212 -0
- package/rules/text/lint/docs/run-v8r.md +197 -0
- package/rules/vue/docs/fix.md +127 -0
- package/rules/vue/js/docs/packages.md +335 -0
- package/rules/vue/lib/docs/vue-forbidden-imports.md +261 -0
- package/rules/worktree/docs/fix.md +161 -0
- package/schemas/rule-meta.json +5 -1
- package/scripts/auto-rules.mjs +7 -4
- package/scripts/coverage-classify/docs/apply.md +202 -0
- package/scripts/coverage-classify/docs/cache.md +203 -0
- package/scripts/coverage-classify/docs/index.md +218 -0
- package/scripts/coverage-classify/docs/prompt.md +132 -0
- package/scripts/coverage-classify/docs/verdict-schema.md +169 -0
- package/scripts/coverage-fix-extract.mjs +122 -0
- package/scripts/coverage-fix.mjs +1 -1
- package/scripts/dispatcher/docs/graph.md +346 -0
- package/scripts/dispatcher/docs/index.md +236 -0
- package/scripts/dispatcher/docs/trace.md +296 -0
- package/scripts/dispatcher/index.mjs +1 -1
- package/scripts/dispatcher/lib/active.mjs +4 -8
- package/scripts/dispatcher/lib/commands.mjs +7 -11
- package/scripts/dispatcher/lib/docs/active.md +348 -0
- package/scripts/dispatcher/lib/docs/artifact.md +232 -0
- package/scripts/dispatcher/lib/docs/budget.md +167 -0
- package/scripts/dispatcher/lib/docs/capability.md +196 -0
- package/scripts/dispatcher/lib/docs/commands.md +210 -0
- package/scripts/dispatcher/lib/docs/events.md +182 -0
- package/scripts/dispatcher/lib/docs/executor.md +190 -0
- package/scripts/dispatcher/lib/docs/flow-lock.md +161 -0
- package/scripts/dispatcher/lib/docs/flow-resolve.md +267 -0
- package/scripts/dispatcher/lib/docs/gate.md +231 -0
- package/scripts/dispatcher/lib/docs/level.md +335 -0
- package/scripts/dispatcher/lib/docs/plan-panel.md +181 -0
- package/scripts/dispatcher/lib/docs/plan.md +200 -0
- package/scripts/dispatcher/lib/docs/planner.md +269 -0
- package/scripts/dispatcher/lib/docs/review.md +255 -0
- package/scripts/dispatcher/lib/docs/reviewer.md +240 -0
- package/scripts/dispatcher/lib/docs/snapshot.md +247 -0
- package/scripts/dispatcher/lib/docs/spec.md +203 -0
- package/scripts/dispatcher/lib/docs/state-store.md +303 -0
- package/scripts/dispatcher/lib/docs/subagent-runner.md +173 -0
- package/scripts/dispatcher/lib/executor.mjs +6 -1
- package/scripts/dispatcher/lib/flow-resolve.mjs +3 -1
- package/scripts/dispatcher/lib/level.mjs +29 -3
- package/scripts/dispatcher/lib/review.mjs +1 -1
- package/scripts/dispatcher/lib/subagent-runner.mjs +5 -3
- package/scripts/docs/auto-rules.md +376 -0
- package/scripts/docs/auto-skills.md +173 -0
- package/scripts/docs/build-agents-commands.md +183 -0
- package/scripts/docs/cli-entry.md +153 -0
- package/scripts/docs/coverage-fix.md +177 -0
- package/scripts/docs/ensure-nitra-cursor-dev-dependencies.md +189 -0
- package/scripts/lib/changed-files.mjs +4 -1
- package/scripts/lib/diff-added-lines.mjs +85 -0
- package/scripts/lib/docs/changed-files.md +149 -0
- package/scripts/lib/docs/check-mdc-template-refs.md +222 -0
- package/scripts/lib/docs/check-reporter.md +175 -0
- package/scripts/lib/docs/discover-check-rules-from-cursor.md +157 -0
- package/scripts/lib/docs/discover-checkable-rules.md +165 -0
- package/scripts/lib/docs/ensure-tool.md +254 -0
- package/scripts/lib/docs/generated-markdown.md +275 -0
- package/scripts/lib/docs/gha-workflow.md +326 -0
- package/scripts/lib/docs/inline-template-links.md +303 -0
- package/scripts/lib/docs/list-rule-ids.md +156 -0
- package/scripts/lib/docs/load-cursor-config.md +147 -0
- package/scripts/lib/docs/mirror-parity.md +167 -0
- package/scripts/lib/worktree.mjs +26 -0
- package/scripts/worktree-cli.mjs +12 -2
- package/skills/coverage-fix/SKILL.md +34 -45
- package/skills/docgen/SKILL.md +44 -23
- package/skills/docgen/bench/etalon/firebase_hosting.md +19 -0
- package/skills/docgen/bench/etalon/k8s-tree.md +24 -0
- package/skills/docgen/bench/etalon/overlay-paths.md +24 -0
- package/skills/docgen/js/docgen-ignore.mjs +54 -0
- package/skills/docgen/js/docgen-scan.mjs +37 -21
- package/skills/llm-patch/SKILL.md +23 -2
- package/skills/start-check/SKILL.md +26 -53
- package/skills/start-check/js/check.mjs +211 -0
- package/skills/taze/SKILL.md +9 -3
- package/skills/taze/js/diff.mjs +154 -0
- package/types/bin/n-cursor.d.ts +1 -1
- package/skills/fix-tests/SKILL.md +0 -119
- package/skills/fix-tests/meta.json +0 -1
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# generated-markdown.mjs
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Модуль `generated-markdown.mjs` містить набір чистих утиліт для генерації згенерованих маркдаун-файлів (зокрема `AGENTS.md` та `CLAUDE.md`) у межах CLI `n-cursor`. Він виконує дві основні задачі:
|
|
6
|
+
|
|
7
|
+
1. Розгортає Mustache-подібні блоки `{{#section}}…{{/section}}` із простою підстановкою одного поля `{{prop}}` для кожного елемента переданого масиву.
|
|
8
|
+
2. Нормалізує підсумковий markdown, не лишаючи послідовностей із трьох і більше `\n` (тобто двох і більше порожніх рядків поспіль), щоб результат відповідав правилу markdownlint **MD012** (no multiple blank lines).
|
|
9
|
+
|
|
10
|
+
Усі функції модуля — суто функціональні (без побічних ефектів, без I/O, без залежностей від глобального стану). Вхід — рядки та звичайні JS-обʼєкти, вихід — рядок.
|
|
11
|
+
|
|
12
|
+
Файл реалізовано як ES-модуль (ESM), розширення `.mjs`, експорти `export function`.
|
|
13
|
+
|
|
14
|
+
## Експорти / API
|
|
15
|
+
|
|
16
|
+
Модуль експортує чотири іменовані функції:
|
|
17
|
+
|
|
18
|
+
| Експорт | Тип | Призначення |
|
|
19
|
+
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
|
20
|
+
| `collapseMultipleBlankLines` | `(text: string) => string` | Згортає 3+ послідовних `\n` до рівно `\n\n`. |
|
|
21
|
+
| `expandMustacheSection` | `(template: string, section: string, items: Record<string,string>[], prop: string) => string` | Розгортає блок `{{#section}}…{{/section}}` для кожного елемента масиву `items`, підставляючи `item[prop]` замість `{{prop}}`. |
|
|
22
|
+
| `renderAgentsTemplate` | `(templateText: string, mdcBasenames: string[], skillItems: { name: string }[], commandItems: { name: string }[]) => string` | Високорівневий рендерер шаблону `AGENTS.template.md`: підставляє списки правил, скілів і команд та нормалізує порожні рядки. |
|
|
23
|
+
| `formatGeneratedMarkdownLines` | `(lines: string[]) => string` | Збирає масив рядків у єдиний markdown-документ із гарантованим завершальним `\n` і без подвійних порожніх рядків. |
|
|
24
|
+
|
|
25
|
+
Усі функції детерміновані: для однакових входів повертають однаковий вихід.
|
|
26
|
+
|
|
27
|
+
## Функції
|
|
28
|
+
|
|
29
|
+
### `collapseMultipleBlankLines(text)`
|
|
30
|
+
|
|
31
|
+
**Сигнатура**
|
|
32
|
+
|
|
33
|
+
```js
|
|
34
|
+
export function collapseMultipleBlankLines(text)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Параметри**
|
|
38
|
+
|
|
39
|
+
- `text` (`string`) — вихідний markdown або будь-який текст. Якщо передано не-рядок, він буде явно скастовано через `String(text)`.
|
|
40
|
+
|
|
41
|
+
**Повертає**
|
|
42
|
+
|
|
43
|
+
- `string` — той самий текст, у якому всі послідовності з трьох і більше `\n` замінено на рівно два `\n` (тобто між блоками лишається не більше ніж один порожній рядок).
|
|
44
|
+
|
|
45
|
+
**Алгоритм**
|
|
46
|
+
|
|
47
|
+
1. Виконує `String(text)` — захист від нерядкового входу (наприклад, `null`, `undefined`, число, обʼєкт).
|
|
48
|
+
2. Викликає `replaceAll(/\n{3,}/g, '\n\n')` — глобальна заміна всіх послідовностей `\n{3,}` на `\n\n`.
|
|
49
|
+
|
|
50
|
+
**Side effects**
|
|
51
|
+
|
|
52
|
+
- Немає. Функція чиста.
|
|
53
|
+
|
|
54
|
+
**Особливості**
|
|
55
|
+
|
|
56
|
+
- Послідовності `\n\n` (один порожній рядок) залишаються без змін.
|
|
57
|
+
- Працює лише з символом `\n`; `\r\n` (CRLF) не нормалізується явно — якщо вхід містить CRLF, регулярка не спрацює на парах `\r\n\r\n\r\n` як на трьох `\n`.
|
|
58
|
+
|
|
59
|
+
### `expandMustacheSection(template, section, items, prop)`
|
|
60
|
+
|
|
61
|
+
**Сигнатура**
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
export function expandMustacheSection(template, section, items, prop)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Параметри**
|
|
68
|
+
|
|
69
|
+
- `template` (`string`) — вихідний текст шаблону, що може містити нуль чи більше блоків `{{#section}}…{{/section}}`.
|
|
70
|
+
- `section` (`string`) — імʼя секції (наприклад, `services`, `skills`, `commands`). Підставляється у відкриваючий тег `{{#section}}` і закриваючий `{{/section}}`.
|
|
71
|
+
- `items` (`Record<string, string>[]`) — масив елементів-обʼєктів. Для кожного елемента тіло секції повторюється один раз.
|
|
72
|
+
- `prop` (`string`) — імʼя поля, значення якого підставляється замість `{{prop}}` у тілі секції. Значення приводиться до рядка через `String(item[prop])`.
|
|
73
|
+
|
|
74
|
+
**Повертає**
|
|
75
|
+
|
|
76
|
+
- `string` — текст шаблону, у якому всі знайдені блоки `{{#section}}…{{/section}}` замінено на згенерований вміст. Якщо блоків немає — повертає вхідний рядок без змін.
|
|
77
|
+
|
|
78
|
+
**Алгоритм**
|
|
79
|
+
|
|
80
|
+
1. Будує константи `open = '{{#${section}}}'`, `close = '{{/${section}}}'`, `placeholder = '{{${prop}}}'`.
|
|
81
|
+
2. У циклі шукає першу позицію `start = indexOf(open)` та `end = indexOf(close)` у поточному значенні `result`.
|
|
82
|
+
3. Поки знайдено валідну пару (`start !== -1 && end !== -1 && end > start`):
|
|
83
|
+
- Витягує `inner = result.slice(start + open.length, end).trim()` — тіло секції без обрамних пробілів/переносів.
|
|
84
|
+
- Для кожного `item` з `items` будує рядок `inner.split(placeholder).join(String(item[prop]))` — повна заміна всіх входжень `placeholder` на значення поля.
|
|
85
|
+
- Зʼєднує всі рендери одним `\n` (без зайвих порожніх рядків між елементами).
|
|
86
|
+
- Підставляє згенерований текст замість усього блоку: `result = result.slice(0, start) + rendered + result.slice(end + close.length)`.
|
|
87
|
+
- Перераховує `start` і `end` для наступної ітерації.
|
|
88
|
+
4. Повертає `result`.
|
|
89
|
+
|
|
90
|
+
**Side effects**
|
|
91
|
+
|
|
92
|
+
- Немає. Функція чиста.
|
|
93
|
+
|
|
94
|
+
**Особливості й обмеження**
|
|
95
|
+
|
|
96
|
+
- Блок підставляється лише за умови `end > start`, тобто закриття має йти після відкриття; вкладені секції з тим самим імʼям не підтримуються.
|
|
97
|
+
- `trim()` тіла секції видаляє пробіли/переноси на початку й у кінці — це навмисно, щоб не лишати пустого рядка на стику з оточуючим markdown.
|
|
98
|
+
- Plаceholder `{{prop}}` замінюється через `split(...).join(...)` — це еквівалентно глобальній заміні без побудови RegExp.
|
|
99
|
+
- Якщо `items` порожній — блок замінюється на порожній рядок (`[].join('\n') === ''`).
|
|
100
|
+
- Підстановка `prop` єдина на блок: модуль не підтримує кілька різних змінних усередині одного блоку.
|
|
101
|
+
|
|
102
|
+
### `renderAgentsTemplate(templateText, mdcBasenames, skillItems, commandItems)`
|
|
103
|
+
|
|
104
|
+
**Сигнатура**
|
|
105
|
+
|
|
106
|
+
```js
|
|
107
|
+
export function renderAgentsTemplate(templateText, mdcBasenames, skillItems, commandItems)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Параметри**
|
|
111
|
+
|
|
112
|
+
- `templateText` (`string`) — повний вміст файлу `AGENTS.template.md` із Mustache-блоками `{{#services}}`, `{{#skills}}`, `{{#commands}}` (кожен зі своїм `{{name}}` усередині).
|
|
113
|
+
- `mdcBasenames` (`string[]`) — масив імен файлів правил `*.mdc` з каталогу `.cursor/rules` (саме базові імена, без шляху).
|
|
114
|
+
- `skillItems` (`{ name: string }[]`) — готові рядки для секції Skills у форматі `{ name: '…' }` (зазвичай вже включають bullet/опис).
|
|
115
|
+
- `commandItems` (`{ name: string }[]`) — готові рядки для секції commands у форматі `{ name: '…' }`.
|
|
116
|
+
|
|
117
|
+
**Повертає**
|
|
118
|
+
|
|
119
|
+
- `string` — готовий вміст `AGENTS.md` із розгорнутими секціями та згорнутими подвійними порожніми рядками.
|
|
120
|
+
|
|
121
|
+
**Алгоритм**
|
|
122
|
+
|
|
123
|
+
1. Перетворює `mdcBasenames` на `serviceItems` виду `{ name: '- .cursor/rules/${mdcName}' }` — додає префікс маркера списку й каталог `.cursor/rules/`.
|
|
124
|
+
2. Послідовно викликає `expandMustacheSection` для трьох секцій:
|
|
125
|
+
- `services` із `serviceItems` і ключем `name`;
|
|
126
|
+
- `skills` із `skillItems` і ключем `name`;
|
|
127
|
+
- `commands` із `commandItems` і ключем `name`.
|
|
128
|
+
3. Передає результат у `collapseMultipleBlankLines` і повертає його.
|
|
129
|
+
|
|
130
|
+
**Side effects**
|
|
131
|
+
|
|
132
|
+
- Немає. Файлову систему не читає й не пише — лише трансформує переданий рядок.
|
|
133
|
+
|
|
134
|
+
**Особливості**
|
|
135
|
+
|
|
136
|
+
- Ключ `name` зашитий у функцію — структура `skillItems` і `commandItems` має містити саме поле `name`.
|
|
137
|
+
- Кожен елемент `mdcBasenames` форматується як bullet списку (`- ...`) у єдиному стилі.
|
|
138
|
+
|
|
139
|
+
### `formatGeneratedMarkdownLines(lines)`
|
|
140
|
+
|
|
141
|
+
**Сигнатура**
|
|
142
|
+
|
|
143
|
+
```js
|
|
144
|
+
export function formatGeneratedMarkdownLines(lines)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Параметри**
|
|
148
|
+
|
|
149
|
+
- `lines` (`string[]`) — масив рядків markdown-документа. Кожен елемент — окремий «рядок» (може бути порожнім або містити кілька логічних рядків).
|
|
150
|
+
|
|
151
|
+
**Повертає**
|
|
152
|
+
|
|
153
|
+
- `string` — підсумковий текст, у якому:
|
|
154
|
+
- елементи `lines` зʼєднано через `\n`;
|
|
155
|
+
- послідовності з 3+ `\n` згорнуто до `\n\n` (через `collapseMultipleBlankLines`);
|
|
156
|
+
- гарантовано додано завершальний `\n`, якщо його не було.
|
|
157
|
+
|
|
158
|
+
**Алгоритм**
|
|
159
|
+
|
|
160
|
+
1. `text = lines.join('\n')` — конкатенація через символ переносу рядка.
|
|
161
|
+
2. `collapsed = collapseMultipleBlankLines(text)` — нормалізація порожніх рядків.
|
|
162
|
+
3. Якщо `collapsed.endsWith('\n')` — повернути як є; інакше повернути `collapsed + '\n'`.
|
|
163
|
+
|
|
164
|
+
**Side effects**
|
|
165
|
+
|
|
166
|
+
- Немає.
|
|
167
|
+
|
|
168
|
+
**Особливості**
|
|
169
|
+
|
|
170
|
+
- Завершальний `\n` гарантує, що згенерований файл закінчується новим рядком (вимога багатьох лінтерів і POSIX-конвенції).
|
|
171
|
+
- Жодних відступів чи табуляцій функція не корегує — лише вертикальну щільність.
|
|
172
|
+
|
|
173
|
+
## Залежності
|
|
174
|
+
|
|
175
|
+
### Зовнішні
|
|
176
|
+
|
|
177
|
+
- Жодних. Модуль не імпортує нічого ні з `node:*`, ні з npm-пакетів, ні з інших файлів проєкту.
|
|
178
|
+
|
|
179
|
+
### Внутрішні (між функціями модуля)
|
|
180
|
+
|
|
181
|
+
- `renderAgentsTemplate` використовує `expandMustacheSection` (тричі) і `collapseMultipleBlankLines` (один раз).
|
|
182
|
+
- `formatGeneratedMarkdownLines` використовує `collapseMultipleBlankLines`.
|
|
183
|
+
- `collapseMultipleBlankLines` і `expandMustacheSection` — незалежні низькорівневі будівельні блоки.
|
|
184
|
+
|
|
185
|
+
### Runtime
|
|
186
|
+
|
|
187
|
+
- ECMAScript із підтримкою:
|
|
188
|
+
- `String.prototype.replaceAll` (Node.js ≥ 15);
|
|
189
|
+
- класичних `String.prototype.indexOf`, `slice`, `split`, `join`, `trim`, `endsWith`;
|
|
190
|
+
- синтаксису ESM (`export function`).
|
|
191
|
+
|
|
192
|
+
## Потік виконання / Використання
|
|
193
|
+
|
|
194
|
+
Типовий потік генерації `AGENTS.md`:
|
|
195
|
+
|
|
196
|
+
1. Викликач (CLI `n-cursor`) читає шаблон `AGENTS.template.md` з файлової системи.
|
|
197
|
+
2. Збирає три масиви даних:
|
|
198
|
+
- список `*.mdc`-файлів із `.cursor/rules` (через `fs.readdir` або еквівалент);
|
|
199
|
+
- метадані скілів (`name` уже з bullet/описом);
|
|
200
|
+
- метадані команд (`name` уже з bullet/описом).
|
|
201
|
+
3. Викликає `renderAgentsTemplate(templateText, mdcBasenames, skillItems, commandItems)`.
|
|
202
|
+
4. Записує отриманий рядок у файл `AGENTS.md` (наприклад, через `fs.writeFile`).
|
|
203
|
+
|
|
204
|
+
Типовий потік генерації багаторядкового документа (наприклад, `CLAUDE.md`):
|
|
205
|
+
|
|
206
|
+
1. Викликач формує масив рядків `lines` — заголовки, абзаци, bullet-и тощо. Між логічними секціями може лишатися кілька порожніх рядків.
|
|
207
|
+
2. Викликає `formatGeneratedMarkdownLines(lines)` — отримує цілісний документ із чистими стиками секцій та фінальним `\n`.
|
|
208
|
+
3. Записує результат у файл.
|
|
209
|
+
|
|
210
|
+
Приклад використання `expandMustacheSection` ізольовано:
|
|
211
|
+
|
|
212
|
+
```js
|
|
213
|
+
import { expandMustacheSection } from './generated-markdown.mjs'
|
|
214
|
+
|
|
215
|
+
const tpl = '# Rules\n{{#services}}- {{name}}\n{{/services}}\nEnd'
|
|
216
|
+
const out = expandMustacheSection(tpl, 'services', [{ name: 'a.mdc' }, { name: 'b.mdc' }], 'name')
|
|
217
|
+
// out === '# Rules\n- a.mdc\n- b.mdc\nEnd'
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Приклад використання `collapseMultipleBlankLines`:
|
|
221
|
+
|
|
222
|
+
```js
|
|
223
|
+
import { collapseMultipleBlankLines } from './generated-markdown.mjs'
|
|
224
|
+
|
|
225
|
+
const cleaned = collapseMultipleBlankLines('A\n\n\n\nB')
|
|
226
|
+
// cleaned === 'A\n\nB'
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Приклад використання `formatGeneratedMarkdownLines`:
|
|
230
|
+
|
|
231
|
+
```js
|
|
232
|
+
import { formatGeneratedMarkdownLines } from './generated-markdown.mjs'
|
|
233
|
+
|
|
234
|
+
const md = formatGeneratedMarkdownLines(['# Title', '', '', '', 'Body'])
|
|
235
|
+
// md === '# Title\n\nBody\n'
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Rebuild Test
|
|
239
|
+
|
|
240
|
+
Контрольний приклад для перевірки коректності модуля після рефакторингу:
|
|
241
|
+
|
|
242
|
+
Дано шаблон:
|
|
243
|
+
|
|
244
|
+
```text
|
|
245
|
+
# Agents
|
|
246
|
+
|
|
247
|
+
## Services
|
|
248
|
+
{{#services}}
|
|
249
|
+
{{name}}
|
|
250
|
+
{{/services}}
|
|
251
|
+
|
|
252
|
+
## Skills
|
|
253
|
+
{{#skills}}
|
|
254
|
+
{{name}}
|
|
255
|
+
{{/skills}}
|
|
256
|
+
|
|
257
|
+
## Commands
|
|
258
|
+
{{#commands}}
|
|
259
|
+
{{name}}
|
|
260
|
+
{{/commands}}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
і виклик:
|
|
264
|
+
|
|
265
|
+
```js
|
|
266
|
+
renderAgentsTemplate(template, ['n-bun.mdc', 'n-vue.mdc'], [{ name: '- /n-fix — fix' }], [{ name: '- /n-lint — lint' }])
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Очікувані властивості результату:
|
|
270
|
+
|
|
271
|
+
- секція `services` містить рядки `- .cursor/rules/n-bun.mdc` і `- .cursor/rules/n-vue.mdc`, розділені одним `\n`;
|
|
272
|
+
- секція `skills` містить рядок `- /n-fix — fix`;
|
|
273
|
+
- секція `commands` містить рядок `- /n-lint — lint`;
|
|
274
|
+
- ніде немає трьох поспіль `\n` (тобто не зʼявляються два порожніх рядки підряд);
|
|
275
|
+
- виклик `formatGeneratedMarkdownLines(result.split('\n'))` повертає той самий зміст із гарантованим завершальним `\n`.
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
# gha-workflow.mjs
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Модуль `gha-workflow.mjs` — це набір допоміжних чистих функцій для **структурного аналізу GitHub Actions workflow-файлів** (`.yml`) після їх розбору як YAML.
|
|
6
|
+
|
|
7
|
+
Призначення модуля — замінити крихкий пошук підрядків у сирому тексті workflow-файла на **типобезпечну** перевірку значень `uses:` та `run:` у кроках (`steps`) робіт (`jobs`). Модуль використовується сценаріями перевірки (checkers) проєктних правил:
|
|
8
|
+
|
|
9
|
+
- `check-ga` — загальна перевірка GitHub Actions workflows;
|
|
10
|
+
- `check-js-lint` — перевірка структури `lint-js.yml`;
|
|
11
|
+
- `check-text` — перевірка наявності викликів `bun run lint-text` у CI;
|
|
12
|
+
- `check-style-lint` — перевірка викликів стайл-лінту в CI;
|
|
13
|
+
- `check-npm-module` — перевірка workflow npm-модуля.
|
|
14
|
+
|
|
15
|
+
Крім перевірки значень `uses:` та `run:`, модуль уміє:
|
|
16
|
+
|
|
17
|
+
- розпізнавати локальну composite-action `./.github/actions/setup-bun-deps`;
|
|
18
|
+
- перевіряти, що `actions/checkout@v6` викликається з `with.persist-credentials: false`;
|
|
19
|
+
- виявляти заборонені в CI прапорці `--fix` у викликах `oxlint` та `eslint`;
|
|
20
|
+
- перевіряти включення точного `glob` у списки `on.push.paths` / `on.pull_request.paths`.
|
|
21
|
+
|
|
22
|
+
Модуль виконує лише читання та обчислення — він **не змінює** жодних файлів і **не виконує** жодних команд. Поведінка детермінована й залежить тільки від переданих аргументів.
|
|
23
|
+
|
|
24
|
+
## Експорти / API
|
|
25
|
+
|
|
26
|
+
Усі експорти з модуля — це **named exports** (іменовані експорти), `default export` немає.
|
|
27
|
+
|
|
28
|
+
| Експорт | Тип | Короткий опис |
|
|
29
|
+
| -------------------------------------------- | -------- | ---------------------------------------------------------------------------------------- |
|
|
30
|
+
| `parseWorkflowYaml(content)` | function | Парсить YAML вміст у звичайний об’єкт; при помилці повертає `null`. |
|
|
31
|
+
| `flattenWorkflowSteps(root)` | function | Збирає всі кроки з усіх jobs у плоский список з метаданими `{ jobId, stepIndex, step }`. |
|
|
32
|
+
| `getStepUses(step)` | function | Повертає значення `uses:` кроку або порожній рядок. |
|
|
33
|
+
| `getStepRun(step)` | function | Повертає значення `run:` кроку (підтримує рядок та масив рядків). |
|
|
34
|
+
| `eventPathsIncludeExact(root, event, exact)` | function | Перевіряє, чи містить `on.<event>.paths` точне значення glob. |
|
|
35
|
+
| `verifyLintJsWorkflowStructure(root)` | function | Виконує повний набір структурних перевірок для `lint-js.yml`. |
|
|
36
|
+
| `anyRunStepIncludes(root, needle)` | function | Перевіряє, чи містить будь-який `run` кроку заданий підрядок. |
|
|
37
|
+
|
|
38
|
+
Внутрішні (неекспортовані) функції-помічники:
|
|
39
|
+
|
|
40
|
+
- `workflowJobsEntries(root)` — повертає `[jobId, job][]`;
|
|
41
|
+
- `workflowJobSteps(job)` — повертає масив об’єктних кроків job;
|
|
42
|
+
- `hasCheckoutWithPersistCredentialsFalse(steps)` — перевіряє `checkout@v6` з `persist-credentials: false`;
|
|
43
|
+
- `appendCiFixFlagFailures(failures, steps)` — додає у `failures` рядки про заборонені `--fix` у CI.
|
|
44
|
+
|
|
45
|
+
Внутрішні константи модуля:
|
|
46
|
+
|
|
47
|
+
- `CHECKOUT_V6_USES = 'actions/checkout@v6'` — очікувана дія checkout та її версія.
|
|
48
|
+
- `LOCAL_SETUP_BUN_DEPS_MARKER = './.github/actions/setup-bun-deps'` — шлях до локальної composite-action для встановлення Bun-залежностей.
|
|
49
|
+
- `BUNX_OXLINT_FIX_RE = /bunx\s+oxlint[^\n]*--fix/u` — регулярний вираз для виявлення `bunx oxlint ... --fix` в одному рядку.
|
|
50
|
+
|
|
51
|
+
## Функції
|
|
52
|
+
|
|
53
|
+
### `parseWorkflowYaml(content)`
|
|
54
|
+
|
|
55
|
+
**Сигнатура:** `parseWorkflowYaml(content: string): Record<string, unknown> | null`
|
|
56
|
+
|
|
57
|
+
**Параметри:**
|
|
58
|
+
|
|
59
|
+
- `content` — рядок із вмістом workflow-файла `.yml`.
|
|
60
|
+
|
|
61
|
+
**Повертає:**
|
|
62
|
+
|
|
63
|
+
- розібраний YAML як звичайний об’єкт (`Record<string, unknown>`), якщо вміст парситься і має тип `object` та не є `null`;
|
|
64
|
+
- `null` — якщо `yaml.parse` кинув виняток або результат не є об’єктом.
|
|
65
|
+
|
|
66
|
+
**Side effects:** немає. Помилка парсингу мовчки перехоплюється `try/catch`.
|
|
67
|
+
|
|
68
|
+
**Примітки:** ця функція безпечна для викликача — навіть на некоректному YAML вона не падає, а повертає `null`, що далі обробляється у `verifyLintJsWorkflowStructure` як спеціальний випадок.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
### `flattenWorkflowSteps(root)`
|
|
73
|
+
|
|
74
|
+
**Сигнатура:** `flattenWorkflowSteps(root: Record<string, unknown>): { jobId: string, stepIndex: number, step: Record<string, unknown> }[]`
|
|
75
|
+
|
|
76
|
+
**Параметри:**
|
|
77
|
+
|
|
78
|
+
- `root` — корінь розібраного YAML (об’єкт із полем `jobs`).
|
|
79
|
+
|
|
80
|
+
**Повертає:** плоский масив об’єктів, кожен з яких містить:
|
|
81
|
+
|
|
82
|
+
- `jobId` — ім’я job (ключ у `jobs`);
|
|
83
|
+
- `stepIndex` — порядковий номер кроку всередині `steps` цього job (починається з 0);
|
|
84
|
+
- `step` — сам об’єкт кроку.
|
|
85
|
+
|
|
86
|
+
**Side effects:** немає.
|
|
87
|
+
|
|
88
|
+
**Алгоритм:** ітерує через `workflowJobsEntries(root)`, для кожного job отримує `workflowJobSteps(job)`, нумерує кроки за допомогою `Array.prototype.entries()` та пуш у акумулятор. Невалідні (необ’єктні) jobs та невалідні steps пропускаються.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
### `getStepUses(step)`
|
|
93
|
+
|
|
94
|
+
**Сигнатура:** `getStepUses(step: Record<string, unknown>): string`
|
|
95
|
+
|
|
96
|
+
**Параметри:**
|
|
97
|
+
|
|
98
|
+
- `step` — об’єкт одного елемента масиву `steps`.
|
|
99
|
+
|
|
100
|
+
**Повертає:** значення `step.uses`, якщо це рядок; інакше — порожній рядок `''`.
|
|
101
|
+
|
|
102
|
+
**Side effects:** немає.
|
|
103
|
+
|
|
104
|
+
**Призначення:** уніфікований доступ до значення `uses:` без перевірок типу у викликачів.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
### `getStepRun(step)`
|
|
109
|
+
|
|
110
|
+
**Сигнатура:** `getStepRun(step: Record<string, unknown>): string`
|
|
111
|
+
|
|
112
|
+
**Параметри:**
|
|
113
|
+
|
|
114
|
+
- `step` — об’єкт одного елемента масиву `steps`.
|
|
115
|
+
|
|
116
|
+
**Повертає:** текст команди `run:`:
|
|
117
|
+
|
|
118
|
+
- якщо `step.run` — рядок, повертається як є;
|
|
119
|
+
- якщо `step.run` — масив, кожен елемент конвертується через `String(...)` та з’єднується через `\n`;
|
|
120
|
+
- інакше — `''`.
|
|
121
|
+
|
|
122
|
+
**Side effects:** немає.
|
|
123
|
+
|
|
124
|
+
**Примітки:** YAML дозволяє запис `run:` як багаторядкового скаляра (`|` / `>-`) або як масиву рядків. Функція нормалізує обидва випадки до одного `string`, який потім зручно перевіряти через `.includes(...)` або регулярним виразом.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
### `eventPathsIncludeExact(root, event, exact)`
|
|
129
|
+
|
|
130
|
+
**Сигнатура:** `eventPathsIncludeExact(root: Record<string, unknown>, event: 'push' | 'pull_request', exact: string): boolean`
|
|
131
|
+
|
|
132
|
+
**Параметри:**
|
|
133
|
+
|
|
134
|
+
- `root` — корінь workflow;
|
|
135
|
+
- `event` — ім’я ключа в `on`: `'push'` або `'pull_request'`;
|
|
136
|
+
- `exact` — очікуваний рядок (glob), який має бути присутній у `paths`.
|
|
137
|
+
|
|
138
|
+
**Повертає:** `true`, якщо у `root.on[event].paths` є масив і він містить точне значення `exact`. У всіх інших випадках (відсутній `on`, відсутній `event`, `paths` не масив тощо) — `false`.
|
|
139
|
+
|
|
140
|
+
**Side effects:** немає.
|
|
141
|
+
|
|
142
|
+
**Гарантії безпеки:** функція захищена від відсутніх та некоректних типів проміжних об’єктів, тому її можна викликати на будь-якому `root`, повернутому з `parseWorkflowYaml`.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
### `verifyLintJsWorkflowStructure(root)`
|
|
147
|
+
|
|
148
|
+
**Сигнатура:** `verifyLintJsWorkflowStructure(root: Record<string, unknown> | null): { ok: boolean, failures: string[] }`
|
|
149
|
+
|
|
150
|
+
**Параметри:**
|
|
151
|
+
|
|
152
|
+
- `root` — корінь розібраного workflow або `null`, якщо парсинг не вдався.
|
|
153
|
+
|
|
154
|
+
**Повертає:** об’єкт результату:
|
|
155
|
+
|
|
156
|
+
- `{ ok: true, failures: [] }` — усі перевірки пройдено;
|
|
157
|
+
- `{ ok: false, failures: [...] }` — список причин відмови у вигляді людинозрозумілих українських повідомлень.
|
|
158
|
+
|
|
159
|
+
**Side effects:** немає.
|
|
160
|
+
|
|
161
|
+
**Перевірки, які виконуються (у порядку додавання до `failures`):**
|
|
162
|
+
|
|
163
|
+
1. Якщо `root === null` — повертається одразу `{ ok: false, failures: ['YAML не вдалося розібрати — перевір синтаксис workflow'] }`.
|
|
164
|
+
2. У жодному кроці немає `uses:` з підрядком `'actions/checkout@v6'` → `'немає кроку uses: actions/checkout@v6'`.
|
|
165
|
+
3. Серед кроків з `actions/checkout@v6` немає такого, що містить `with.persist-credentials === false` → `'checkout@v6 без with.persist-credentials: false'`.
|
|
166
|
+
4. У жодному кроці немає `uses:` з підрядком `'./.github/actions/setup-bun-deps'` → `'немає uses: ./.github/actions/setup-bun-deps'`.
|
|
167
|
+
5. У сумарному `run`-блобі немає `'bunx oxlint'` → `'у run немає bunx oxlint'`.
|
|
168
|
+
6. У сумарному `run`-блобі немає `'bunx eslint .'` → `'у run немає bunx eslint .'`.
|
|
169
|
+
7. У сумарному `run`-блобі немає `'bunx jscpd .'` → `'у run немає bunx jscpd .'`.
|
|
170
|
+
8. Для кожного кроку, чий `run` матчиться `BUNX_OXLINT_FIX_RE`, додається `'у run є oxlint з --fix (у CI заборонено)'`.
|
|
171
|
+
9. Для кожного кроку, чий `run` містить `'eslint --fix'`, додається `'у run є eslint --fix (у CI заборонено)'`.
|
|
172
|
+
|
|
173
|
+
**Примітка:** «сумарний `run`-блоб» — це `flattenWorkflowSteps(root).map(s => getStepRun(s.step)).join('\n')`. Тобто перевірки 5–7 пасять, навіть якщо `bunx oxlint`, `bunx eslint .` та `bunx jscpd .` рознесені по різних кроках.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
### `anyRunStepIncludes(root, needle)`
|
|
178
|
+
|
|
179
|
+
**Сигнатура:** `anyRunStepIncludes(root: Record<string, unknown>, needle: string): boolean`
|
|
180
|
+
|
|
181
|
+
**Параметри:**
|
|
182
|
+
|
|
183
|
+
- `root` — корінь workflow;
|
|
184
|
+
- `needle` — підрядок для пошуку в текстах `run:`.
|
|
185
|
+
|
|
186
|
+
**Повертає:** `true`, якщо знайдено принаймні один крок, у `run:` якого є `needle`; інакше `false`.
|
|
187
|
+
|
|
188
|
+
**Side effects:** немає. Ітерація припиняється на першому збігу (рання передача).
|
|
189
|
+
|
|
190
|
+
**Типовий приклад:** `anyRunStepIncludes(root, 'bun run lint-text')` для `check-text` — перевірити, що CI взагалі викликає таргет лінту текстів.
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
### `workflowJobsEntries(root)` (internal)
|
|
195
|
+
|
|
196
|
+
**Сигнатура:** `workflowJobsEntries(root: Record<string, unknown>): [string, Record<string, unknown>][]`
|
|
197
|
+
|
|
198
|
+
**Параметри:** `root` — корінь workflow.
|
|
199
|
+
|
|
200
|
+
**Повертає:** список пар `[jobId, job]` для тих ключів `jobs`, у яких значення є непорожнім об’єктом.
|
|
201
|
+
|
|
202
|
+
**Side effects:** немає.
|
|
203
|
+
|
|
204
|
+
**Алгоритм:** перевіряє наявність та тип `root.jobs`, далі `Object.entries(jobs).flatMap(...)` фільтрує невалідні значення (масив порожніх або одно-елементних масивів).
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
### `workflowJobSteps(job)` (internal)
|
|
209
|
+
|
|
210
|
+
**Сигнатура:** `workflowJobSteps(job: Record<string, unknown>): Record<string, unknown>[]`
|
|
211
|
+
|
|
212
|
+
**Параметри:** `job` — один job-об’єкт.
|
|
213
|
+
|
|
214
|
+
**Повертає:** масив об’єктних кроків з `job.steps`; невалідні (необ’єктні / `null`) елементи фільтруються.
|
|
215
|
+
|
|
216
|
+
**Side effects:** немає.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
### `hasCheckoutWithPersistCredentialsFalse(steps)` (internal)
|
|
221
|
+
|
|
222
|
+
**Сигнатура:** `hasCheckoutWithPersistCredentialsFalse(steps: { step: Record<string, unknown> }[]): boolean`
|
|
223
|
+
|
|
224
|
+
**Параметри:** `steps` — результат `flattenWorkflowSteps` (використовується тільки поле `step`).
|
|
225
|
+
|
|
226
|
+
**Повертає:** `true`, якщо знайдено крок, у якого:
|
|
227
|
+
|
|
228
|
+
- `uses` містить `'actions/checkout@v6'`;
|
|
229
|
+
- `step.with` — об’єкт;
|
|
230
|
+
- `step.with['persist-credentials'] === false` (саме `false`, а не «фолсі»).
|
|
231
|
+
|
|
232
|
+
**Side effects:** немає.
|
|
233
|
+
|
|
234
|
+
**Призначення:** перевірити, що `actions/checkout@v6` явно вимкнув збереження токена в git-конфізі — це вимога безпеки в правилі `n-ga`.
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
### `appendCiFixFlagFailures(failures, steps)` (internal)
|
|
239
|
+
|
|
240
|
+
**Сигнатура:** `appendCiFixFlagFailures(failures: string[], steps: { step: Record<string, unknown> }[]): void`
|
|
241
|
+
|
|
242
|
+
**Параметри:**
|
|
243
|
+
|
|
244
|
+
- `failures` — акумулятор-масив, у який функція **пушить** нові рядки помилок;
|
|
245
|
+
- `steps` — результат `flattenWorkflowSteps`.
|
|
246
|
+
|
|
247
|
+
**Повертає:** `undefined` (мутує `failures`).
|
|
248
|
+
|
|
249
|
+
**Side effects:** мутація переданого масиву `failures` через `Array.prototype.push`.
|
|
250
|
+
|
|
251
|
+
**Логіка:** для кожного кроку:
|
|
252
|
+
|
|
253
|
+
- якщо `BUNX_OXLINT_FIX_RE.test(run)` — додає повідомлення про заборонений `--fix` у `bunx oxlint`;
|
|
254
|
+
- якщо `run.includes('eslint --fix')` — додає повідомлення про заборонений `eslint --fix`.
|
|
255
|
+
|
|
256
|
+
**Примітка:** функція може додати **обидва** повідомлення для одного і того ж кроку, якщо в `run` присутні обидва патерни. Якщо в різних кроках присутній один і той самий патерн — повідомлення додасться **кілька разів** (по одному на крок).
|
|
257
|
+
|
|
258
|
+
## Залежності
|
|
259
|
+
|
|
260
|
+
**Зовнішні npm-пакети:**
|
|
261
|
+
|
|
262
|
+
- `yaml` — функція `parse(content)` для розбору YAML у JS-об’єкт. Імпорт іменований: `import { parse } from 'yaml'`.
|
|
263
|
+
|
|
264
|
+
**Стандартна бібліотека JS:** `Object.entries`, `Array.isArray`, `Array.prototype.flatMap`, `Array.prototype.map`, `Array.prototype.entries`, `Array.prototype.some`, `Array.prototype.includes`, `Array.prototype.join`, `RegExp.prototype.test`, `String.prototype.includes`.
|
|
265
|
+
|
|
266
|
+
**Внутрішні залежності проєкту:** жодних модулів проєкту не імпортує — це листовий хелпер.
|
|
267
|
+
|
|
268
|
+
**Хто залежить від цього модуля (зворотні залежності):** згідно з docstring файла — скрипти `check-ga`, `check-js-lint`, `check-text`, `check-style-lint`, `check-npm-module`. Конкретні шляхи до цих скриптів живуть у `npm/scripts/` / `npm/checks/` та використовують іменовані експорти модуля.
|
|
269
|
+
|
|
270
|
+
## Потік виконання / Використання
|
|
271
|
+
|
|
272
|
+
### Типовий цикл використання сценарієм-checker
|
|
273
|
+
|
|
274
|
+
1. Сценарій читає вміст файла `.github/workflows/<name>.yml` як рядок (наприклад через `node:fs/promises`).
|
|
275
|
+
2. Викликає `const root = parseWorkflowYaml(content)`.
|
|
276
|
+
3. Якщо `root === null` — повідомляє про синтаксичну помилку YAML.
|
|
277
|
+
4. Інакше викликає одну зі спеціалізованих перевірок (наприклад `verifyLintJsWorkflowStructure(root)`) або серію загальних (`flattenWorkflowSteps`, `getStepUses`, `getStepRun`, `anyRunStepIncludes`, `eventPathsIncludeExact`).
|
|
278
|
+
5. На основі результату формує звіт перевірки.
|
|
279
|
+
|
|
280
|
+
### Приклад: перевірка `lint-js.yml`
|
|
281
|
+
|
|
282
|
+
```js
|
|
283
|
+
import { readFile } from 'node:fs/promises'
|
|
284
|
+
import { parseWorkflowYaml, verifyLintJsWorkflowStructure } from './gha-workflow.mjs'
|
|
285
|
+
|
|
286
|
+
const content = await readFile('.github/workflows/lint-js.yml', 'utf8')
|
|
287
|
+
const root = parseWorkflowYaml(content)
|
|
288
|
+
const result = verifyLintJsWorkflowStructure(root)
|
|
289
|
+
|
|
290
|
+
if (!result.ok) {
|
|
291
|
+
for (const f of result.failures) {
|
|
292
|
+
console.error('lint-js.yml:', f)
|
|
293
|
+
}
|
|
294
|
+
process.exit(1)
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Приклад: перевірка наявності таргета `bun run lint-text`
|
|
299
|
+
|
|
300
|
+
```js
|
|
301
|
+
import { parseWorkflowYaml, anyRunStepIncludes } from './gha-workflow.mjs'
|
|
302
|
+
|
|
303
|
+
const root = parseWorkflowYaml(content)
|
|
304
|
+
if (root && !anyRunStepIncludes(root, 'bun run lint-text')) {
|
|
305
|
+
console.error('у CI відсутній виклик bun run lint-text')
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Приклад: перевірка `on.pull_request.paths`
|
|
310
|
+
|
|
311
|
+
```js
|
|
312
|
+
import { parseWorkflowYaml, eventPathsIncludeExact } from './gha-workflow.mjs'
|
|
313
|
+
|
|
314
|
+
const root = parseWorkflowYaml(content)
|
|
315
|
+
if (root && !eventPathsIncludeExact(root, 'pull_request', '**/*.vue')) {
|
|
316
|
+
console.error('on.pull_request.paths не містить **/*.vue')
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Властивості, корисні викликачам
|
|
321
|
+
|
|
322
|
+
- **Чисті функції.** Жодних бічних ефектів (крім явної мутації `failures` в `appendCiFixFlagFailures`, яка інкапсульована всередині `verifyLintJsWorkflowStructure`).
|
|
323
|
+
- **Безпечність до помилок типів.** Усі публічні функції захищені від некоректних або відсутніх ключів — повертають `''`, `false` або `[]` замість падіння.
|
|
324
|
+
- **Уніфікований доступ.** `getStepUses` та `getStepRun` нормалізують форму YAML (рядок vs масив), щоб викликач завжди працював із `string`.
|
|
325
|
+
- **Точне `paths`-зіставлення.** `eventPathsIncludeExact` вимагає **точного** елемента масиву, а не підрядка — тому glob-патерни мають бути записані як є.
|
|
326
|
+
- **Сумарний `run`-блоб.** У `verifyLintJsWorkflowStructure` пункти 5–7 не вимагають, щоб усі команди жили в одному `run`-кроці — вони можуть бути рознесені по кроках/jobs.
|