spectrum-cli 5.1.0 → 6.0.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/CHANGELOG.md CHANGED
@@ -9,6 +9,21 @@ This is the changelog of the `spectrum-cli` npm package. Its version comes from
9
9
  `package.json`; the release commands use Git tags and `CHANGELOG.md` in the
10
10
  **target project** and do not update that project's `package.json`.
11
11
 
12
+ ## 🚀 [6.0.0] - 2026-10-29
13
+
14
+ ### Breaking change
15
+
16
+ - У `spectrum chart start` короткий флаг `-n` теперь означает `--no-verify` и отключает preflight. Для запуска без ожидания публикации чарта используйте `--no-wait`.
17
+
18
+ ### Added
19
+
20
+ - Во всех 21 командах с действием доступен `-n, --no-verify`: команда пропускает preflight и проверку обновлений CLI, сохраняя чтение необходимых входных данных и обработку ошибок операций. Поддерживается сочетание `-ns`; `--dry` и `--no-verify` несовместимы.
21
+
22
+ ### Changed
23
+
24
+ - README содержит полный список команд, аргументов и опций; справка вложенных команд использует общий формат и показывает актуальные флаги.
25
+ - Добавлены проверки разбора флагов у всех команд, соответствия README регистрации команд и выполнения release/hotfix без preflight на временных Git origins, а также обхода preflight автоматизации.
26
+
12
27
  ## 🚀 [5.1.0]
13
28
 
14
29
  ### Changed
package/README.md CHANGED
@@ -18,7 +18,8 @@ spectrum --help
18
18
  в npm Registry. Если доступна новая версия, показывает текущую и новую версии и
19
19
  команду `spectrum upgrade`. Она запускает `npm install -g spectrum-cli`.
20
20
  Проверка ограничена 200 мс; недоступность Registry не влияет на выполнение
21
- команды и не выводит ошибку.
21
+ команды и не выводит ошибку. С `--dry` или `--no-verify` проверка обновлений
22
+ не выполняется.
22
23
 
23
24
  ### 🔧 Из исходников
24
25
 
@@ -42,6 +43,9 @@ spectrum --help
42
43
  # Показать текущую версию CLI
43
44
  spectrum -v # или spectrum --version
44
45
 
46
+ # Обновить установленный CLI
47
+ spectrum upgrade
48
+
45
49
  # 🚀 Управление релизами
46
50
  spectrum release start [--stable] # Схлопнуть fragments и атомарно отправить release commit в dev и main/master
47
51
  spectrum release deploy # Создать стабильный тег release/X.Y.Z из заголовка CHANGELOG
@@ -54,7 +58,7 @@ spectrum hotfix close # После stable pipeline: main/master
54
58
 
55
59
  # 📈 Теги chart
56
60
  spectrum chart start # Создать тег по CHANGELOG.md чарта и дождаться публикации в Helm-registry
57
- spectrum chart start -n # Не ждать публикации после отправки тега
61
+ spectrum chart start --no-wait # Не ждать публикации после отправки тега
58
62
  spectrum chart start --force # Разрешить версию не выше последнего remote-тега, если ее еще нет в registry и Git
59
63
  spectrum chart deploy # Обновить helmrelease.yaml до последнего chart-тега и запушить
60
64
  spectrum chart deploy --instances sd,cbch # Обновить только выбранные инстансы instances/<name>
@@ -67,12 +71,42 @@ spectrum changelog write # Выравнять заголовки C
67
71
 
68
72
  # 🔑 GitLab токены
69
73
  spectrum token rotate # Пролить SC_BOT_PAT в CI variables
74
+
75
+ # CI: Next.js, SBOM и security
76
+ spectrum nextjs licenses
77
+ spectrum sbom catalog resolve
78
+ spectrum sbom catalog populate
79
+ spectrum security scan
80
+ spectrum security track
81
+ spectrum security reconcile
82
+ spectrum security repair
70
83
  ```
71
84
 
72
- Все команды с действием показывают, какая предпроверка успешно пройдена;
85
+ По умолчанию команды с действием показывают, какая предпроверка успешно пройдена;
73
86
  при ошибке выводят её имя и причину. Независимые предпроверки продолжают
74
87
  выполняться после ошибки; зависимые проверки без нужных данных помечаются как
75
88
  невыполненные. Шаги команды запускаются, только если все проверки прошли.
89
+
90
+ `-n, --no-verify` доступен во всех 21 командах с действием: пропускает
91
+ preflight и проверку обновлений CLI, затем запускает действия команды.
92
+ Чтение необходимых входных данных (версии, fragments, пути, конфигурация,
93
+ токены) остаётся частью подготовки к действию. Ошибки чтения и самих операций
94
+ по-прежнему останавливают выполнение. Флаг не отменяет выбор типа fragment,
95
+ запрос токенов, подтверждение публикации chart или ожидание registry.
96
+ Для отключения ожидания у `chart start` отдельно укажите `--no-wait`.
97
+ Прежний короткий флаг `chart start -n` теперь означает `--no-verify`.
98
+ Release/hotfix используют локальные теги и сохранённые refs `origin/*`
99
+ для определения базовой версии, без preflight fetch и сверки с сервером.
100
+
101
+ ```bash
102
+ spectrum release start -n
103
+ spectrum chart start --no-verify --no-wait
104
+ spectrum security repair -ns
105
+ ```
106
+
107
+ `--dry` и `--no-verify` несовместимы: CLI завершится с ошибкой до действий.
108
+ Без `--no-verify` сохраняются обычные предпроверки.
109
+
76
110
  `-s, --silence` скрывает сообщения об успешно пройденных предпроверках.
77
111
  Ошибки проверок и итоговое сообщение команды остаются видны; флаг можно
78
112
  совмещать с `-d, --dry`.
@@ -88,8 +122,10 @@ GitLab; `token rotate -d` запрашивает токены и проверя
88
122
  | --- | --- | --- |
89
123
  | `-d` | `--dry` | Все команды с действием: только предпроверки |
90
124
  | `-s` | `--silence` | Все команды с действием: скрыть успешные предпроверки |
91
- | `-f` | `--force` | `chart start`: разрешить версию не больше последнего тега на `origin`; проверки Git-тега и Helm-registry обязательны |
92
- | `-n` | `--no-wait` | `chart start`: не ждать появления версии в Helm-registry после push; по умолчанию команда ждёт |
125
+ | `-f` | `--force` | `chart start`: разрешить версию не больше последнего тега на `origin`; остальные preflight сохраняются, если не задан `--no-verify` |
126
+ | `-n` | `--no-verify` | Все команды с действием: пропустить preflight и сразу выполнить действия |
127
+ | — | `--no-wait` | `chart start`: не ждать появления версии в Helm-registry после push; по умолчанию команда ждёт |
128
+ | — | `--stable` | `release start`: переход с 0.x на 1.0.0 |
93
129
  | `-i <names>` | `--instances <names>` | `chart deploy`: выбрать инстансы через запятую |
94
130
  | `-h` | `--help` | Справка CLI и команд |
95
131
  | `-v` | `--version` | Версия CLI |
@@ -100,6 +136,9 @@ GitLab; `token rotate -d` запрашивает токены и проверя
100
136
  # Справка по релизам
101
137
  spectrum release --help
102
138
 
139
+ # Справка по хотфиксам
140
+ spectrum hotfix --help
141
+
103
142
  # Справка по changelog
104
143
  spectrum changelog --help
105
144
 
@@ -108,6 +147,11 @@ spectrum chart --help
108
147
 
109
148
  # Справка по токенам
110
149
  spectrum token --help
150
+
151
+ # Справка по CI-командам
152
+ spectrum nextjs --help
153
+ spectrum sbom catalog --help
154
+ spectrum security --help
111
155
  ```
112
156
 
113
157
  ## ✨ Особенности
@@ -141,6 +185,10 @@ spectrum-cli/
141
185
  │ ├── hotfix/ # Проверки и этапы hotfix
142
186
  │ ├── token/ # Конфиг, GitLab API и ротация
143
187
  │ ├── integrations/ # Общие функции внешних сервисов
188
+ │ ├── automation/ # Общие операции ввода-вывода для CI
189
+ │ ├── nextjs/ # LICENSE standalone-пакетов
190
+ │ ├── sbom/ # Поиск и публикация OCI-каталогов
191
+ │ └── security/ # Аудит, задачи и исправления
144
192
  ├── __tests__/ # Модульные и интеграционные тесты
145
193
  ├── package.json
146
194
  ├── install.sh
@@ -153,26 +201,45 @@ spectrum-cli/
153
201
 
154
202
  ### Команды
155
203
 
156
- | Команда | Описание |
157
- | --------------------------- | ----------------------------------- |
158
- | `spectrum release start` | Схлопнуть и опубликовать fragments |
159
- | `spectrum release deploy` | Создать стабильный тег release/X.Y.Z |
160
- | `spectrum release close` | Закрыть релиз |
161
- | `spectrum changelog append` | Создать changelog fragment |
162
- | `spectrum changelog check` | Проверить changelog и fragments |
163
- | `spectrum chart start` | Создать и запушить chart тег |
164
- | `spectrum chart deploy` | Обновить chart версию в helmrelease |
165
- | `spectrum chart verify` | Проверить ingress paths chart |
166
- | `spectrum token rotate` | Пролить SC_BOT_PAT в CI |
204
+ Ниже перечислены все команды с действием из `spectrum --help` и справки
205
+ вложенных групп. Каждая поддерживает `-n, --no-verify`, `-d, --dry` и
206
+ `-s, --silence`.
207
+
208
+ | Команда | Аргументы и дополнительные опции | Назначение |
209
+ | --- | --- | --- |
210
+ | `spectrum upgrade` | — | Обновить CLI через npm |
211
+ | `spectrum release start` | `--stable` | Собрать fragments и отправить release commit в dev и main/master |
212
+ | `spectrum release deploy` | — | Создать и отправить стабильный тег `release/X.Y.Z` |
213
+ | `spectrum release close` | — | Свести main/master в dev |
214
+ | `spectrum hotfix start` | — | Подготовить hotfix CHANGELOG локально |
215
+ | `spectrum hotfix deploy` | — | Отправить тег `hotfix/X.Y.Z` после merge в production |
216
+ | `spectrum hotfix close` | — | Свести production в dev после stable pipeline |
217
+ | `spectrum changelog append` | `<message>` | Создать changelog fragment |
218
+ | `spectrum changelog check` | — | Проверить CHANGELOG.md и fragments |
219
+ | `spectrum changelog write` | — | Нормализовать заголовки и отформатировать CHANGELOG.md |
220
+ | `spectrum chart start` | `-f, --force`, `--no-wait` | Создать и отправить chart-тег, дождаться registry |
221
+ | `spectrum chart deploy` | `-i, --instances <names>` | Обновить helmrelease и опубликовать изменения |
222
+ | `spectrum chart verify` | `<source_path>` | Сравнить ingress paths с маршрутами Next.js |
223
+ | `spectrum token rotate` | — | Пролить `SC_BOT_PAT` в CI variables |
224
+ | `spectrum nextjs licenses` | — | Сохранить LICENSE compiled-пакетов в standalone |
225
+ | `spectrum sbom catalog resolve` | — | Найти OCI-каталоги зависимостей образа |
226
+ | `spectrum sbom catalog populate` | — | Построить и опубликовать OCI-каталоги |
227
+ | `spectrum security scan` | — | Проверить npm-зависимости и runtime |
228
+ | `spectrum security track` | — | Найти или создать задачи YouTrack |
229
+ | `spectrum security reconcile` | — | Запустить tracker и получить номера задач |
230
+ | `spectrum security repair` | — | Исправить зависимости, проверить и создать MR |
167
231
 
168
232
  ### 🛡️ Preflight-проверки по командам
169
233
 
234
+ Этот список описывает запуск без `--no-verify`.
235
+
170
236
  - `spectrum release start`: чистая и актуальная dev-ветка, `dev` содержит `origin/main` или `origin/master`, стабильный тег, достижимый из production-ветки, согласованная опубликованная история `CHANGELOG.md` и не более одного открытого релиза. При первом запуске нужны валидные fragments и свободная версия; повторный дополняет открытый раздел без повышения версии. `package.json` не читается.
171
237
  - `spectrum release deploy`: чистая и актуальная main/master, стабильная версия `X.Y.Z` из верхнего заголовка `CHANGELOG.md`, отсутствие этой версии среди локальных и remote-тегов всех трёх форм. Команда не создает RC-теги.
172
238
  - `spectrum release close`: чистая и актуальная main/master, версия из верхнего заголовка `CHANGELOG.md` и remote stable-тег поддерживаемой формы, указывающий на текущий commit.
173
239
  - `spectrum changelog append <message>`: `git-repo`, `changelog-exists`, валидные ID задачи, git identity и тип fragment. Команда не изменяет общий `CHANGELOG.md`.
174
- - `spectrum changelog check`: `git-repo`, `changelog-exists`, `changelog-prettier-check`, наличие и формат всех changelog fragments.
175
- - `spectrum chart start`: `git-repo`, `clean-working-tree`, `on-main-branch`, `single-chart` (ровно один `charts/<chart-name>/Chart.yaml`), валидный верхний SemVer-заголовок в `charts/<chart-name>/CHANGELOG.md`, отсутствие тега `chart-<name>-<version>` локально и на `origin`, отсутствие этой версии чарта в Helm-registry. Для запроса registry нужен `SC_DEVELOPER_PAT` (PAT со scope `api` и доступом к проекту; роль Owner не требуется); ошибка доступа блокирует команду. Право отправки chart-тега определяется отдельно правилами protected tags. `-f, --force` обходит только проверку повышения версии относительно последнего remote-тега. После push команда по умолчанию ждёт версию в registry; `-n, --no-wait` отключает ожидание.
240
+ - `spectrum changelog check`: `git-repo`, `changelog-exists`, `changelog-prettier-check`, наличие и формат всех changelog fragments. С `--no-verify` команда завершается без проверок.
241
+ - `spectrum changelog write`: `git-repo`, `changelog-exists`, доступность Prettier.
242
+ - `spectrum chart start`: `git-repo`, `clean-working-tree`, `on-main-branch`, `single-chart` (ровно один `charts/<chart-name>/Chart.yaml`), валидный верхний SemVer-заголовок в `charts/<chart-name>/CHANGELOG.md`, отсутствие тега `chart-<name>-<version>` локально и на `origin`, отсутствие этой версии чарта в Helm-registry. Для запроса registry нужен `SC_DEVELOPER_PAT` (PAT со scope `api` и доступом к проекту; роль Owner не требуется); ошибка доступа блокирует команду. Право отправки chart-тега определяется отдельно правилами protected tags. `-f, --force` обходит только проверку повышения версии относительно последнего remote-тега. После push команда по умолчанию ждёт версию в registry; `--no-wait` отключает ожидание.
176
243
 
177
244
  Для обратной совместимости chart-команды принимают `SC_OWNER_PAT`, если
178
245
  `SC_DEVELOPER_PAT` не задана или пуста. `SC_OWNER_PAT` deprecated: поддержка
@@ -180,8 +247,8 @@ spectrum-cli/
180
247
  выводит предупреждение с просьбой перейти на `SC_DEVELOPER_PAT`. Если заданы обе
181
248
  переменные, используется `SC_DEVELOPER_PAT` без предупреждения.
182
249
 
183
- - `spectrum chart deploy`: `git-repo`, `clean-working-tree`, `on-main-branch` (текущая ветка `main`), `remote-origin` (настроен `origin`), `remote-reachable` (доступен `origin`), `single-chart`, `helmrelease-files` (найдены `helmrelease.yaml`).
184
- - `spectrum chart verify <source_path>`: `git-repo`, `single-values-yaml` (ровно один `charts/**/values.yaml`), `values-ingress-sections` (есть `ingress.paths.api/pages/assets`), `source-path-directory`, `next-project`, `build-command-support`.
250
+ - `spectrum chart deploy`: `git-repo`, `clean-working-tree`, `on-main-branch` (текущая ветка `main/master`), `branch-up-to-date`, `remote-origin` (настроен `origin`), `remote-reachable` (доступен `origin`), `single-chart`, `helmrelease-files` (найдены `helmrelease.yaml`).
251
+ - `spectrum chart verify <source_path>`: `git-repo`, `branch-up-to-date`, `single-values-yaml` (ровно один `charts/**/values.yaml`), `values-ingress-sections` (есть `ingress.paths.api/pages/assets`), `source-path-directory`, `next-project`, `build-command-support`.
185
252
  - `spectrum token rotate`: `load-config` (есть валидный `~/.config/spectrum-cli/config.yaml`), `ask-tokens` (owner PAT и `SC_BOT_PAT` только в памяти), `check-access` (все группы и проекты доступны owner PAT).
186
253
 
187
254
  ## 🔄 Workflow релиза
@@ -323,7 +390,7 @@ CI-часть реализует подключенный release component, а
323
390
  3. Читает версию из верхнего заголовка `charts/<chart-name>/CHANGELOG.md` и проверяет SemVer
324
391
  4. Собирает тег `chart-<name>-<version>`
325
392
  5. Проверяет, что такого тега еще нет локально и на `origin`, а версия отсутствует в Helm-registry
326
- 6. Создает тег и пушит его в `origin`, затем ждёт появления версии в registry; `-n, --no-wait` отключает ожидание
393
+ 6. Создает тег и пушит его в `origin`, затем ждёт появления версии в registry; `--no-wait` отключает ожидание
327
394
 
328
395
  ### `spectrum chart deploy`
329
396
 
@@ -581,16 +648,17 @@ registry push: эти права проверяются соответствую
581
648
  [YouTrack My User Profile](https://www.jetbrains.com/help/youtrack/devportal/resource-api-users-me.html),
582
649
  [YouTrack project fields](https://www.jetbrains.com/help/youtrack/devportal/resource-api-admin-projects-projectID-customFields.html).
583
650
 
584
- ### Dry и silence для команд автоматизации
651
+ ### No-verify, dry и silence для команд автоматизации
585
652
 
586
653
  Все семь команд `nextjs licenses`, `sbom catalog resolve/populate` и
587
- `security scan/track/reconcile/repair` поддерживают `-d, --dry` и
588
- `-s, --silence`, включая сочетание `-ds`.
654
+ `security scan/track/reconcile/repair` поддерживают `-n, --no-verify`, `-d, --dry` и
655
+ `-s, --silence`, включая сочетания `-ns` и `-ds`.
589
656
 
590
657
  ```sh
591
658
  spectrum nextjs licenses --dry
592
659
  spectrum sbom catalog populate --dry --silence
593
660
  spectrum security repair -ds
661
+ spectrum security repair --no-verify --silence
594
662
  ```
595
663
 
596
664
  `--dry` выполняет предпроверки настроек, файлов, инструментов и доступов,
@@ -607,3 +675,10 @@ Git/проектов; SBOM preflight может загрузить закреп
607
675
  Ошибки, предупреждения, результаты команды и итог dry остаются видимыми.
608
676
  Это тот же смысл флага, что у release/hotfix команд. Без dry выполняются
609
677
  обычные действия и сохраняются прежние коды завершения 0/1/2.
678
+
679
+ `--no-verify` пропускает предварительные проверки доступов GitLab/YouTrack,
680
+ registry и соответствия tracker ref. Команда сразу переходит к своим действиям:
681
+ аудиту, копированию LICENSE, работе с каталогами, задачами или исправлениям.
682
+ Загрузка входных отчётов, аутентификация для операций, проверка целостности
683
+ артефактов и проверочные команды после исправления остаются частью действия.
684
+ Пропущенные preflight не получают статус «проверено» в отчёте.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spectrum-cli",
3
- "version": "5.1.0",
3
+ "version": "6.0.0",
4
4
  "description": "🚀 Modern CLI for development workflow automation - release management, versioning, and changelog generation",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -9,7 +9,7 @@ function changelogAppend(message) {
9
9
  checks: [
10
10
  { name: 'git-repo', run: requireGitRepo },
11
11
  { name: 'changelog-exists', run: () => requireFileExists(CHANGELOG_FILE) },
12
- { name: 'prepare-fragment', dependsOn: ['git-repo', 'changelog-exists'], run: () => prepareChangelogEntry(message) }
12
+ { name: 'prepare-fragment', prepare: true, dependsOn: ['git-repo', 'changelog-exists'], run: () => prepareChangelogEntry(message) }
13
13
  ],
14
14
  steps: [
15
15
  { name: 'write-fragment', run: appendPreparedChangelogEntry }
@@ -41,6 +41,14 @@ function requireReleaseState(context) {
41
41
  }
42
42
  }
43
43
 
44
+ function readLocalReleaseState(context) {
45
+ const local = splitOpenRelease(fs.readFileSync(CHANGELOG_FILE, 'utf8'), context.stableVersion);
46
+ return { ok: true, data: {
47
+ openReleaseVersion: local.open?.version || null,
48
+ releaseChangelogState: { local, production: { ...local, open: null, entries: [] } }
49
+ } };
50
+ }
51
+
44
52
  function requireReleaseFragments(context) {
45
53
  try {
46
54
  // Empty is valid only for an already prepared release, including retry after a failed push.
@@ -77,4 +85,4 @@ function appendOpenRelease(context) {
77
85
  }
78
86
  }
79
87
 
80
- module.exports = { resolveReleaseState, requireReleaseState, requireReleaseFragments, appendOpenRelease };
88
+ module.exports = { resolveReleaseState, readLocalReleaseState, requireReleaseState, requireReleaseFragments, appendOpenRelease };
@@ -30,10 +30,10 @@ function chartDeploy(options = {}) {
30
30
  { name: 'branch-up-to-date', run: requireCurrentBranchUpToDateWithRemote },
31
31
  { name: 'remote-origin', run: requireRemoteOrigin },
32
32
  { name: 'remote-reachable', run: requireRemoteReachable },
33
- { name: 'single-chart', run: requireSingleChart },
34
- { name: 'helmrelease-files', run: requireHelmReleaseFiles },
33
+ { name: 'single-chart', prepare: true, run: requireSingleChart },
34
+ { name: 'helmrelease-files', prepare: true, run: requireHelmReleaseFiles },
35
35
  {
36
- name: 'filter-instances',
36
+ name: 'filter-instances', prepare: true,
37
37
  run: (ctx) => {
38
38
  const requested = parseInstancesOption(instancesOption);
39
39
  if (requested.length === 0) {
@@ -15,9 +15,9 @@ function chartStart(options = {}) {
15
15
  { name: 'clean-working-tree', run: requireCleanWorkingTree },
16
16
  { name: 'on-main-branch', run: requireOnMainBranch },
17
17
  { name: 'branch-up-to-date', run: requireCurrentBranchUpToDateWithRemote },
18
- { name: 'single-chart', run: requireSingleChart },
18
+ { name: 'single-chart', prepare: true, run: requireSingleChart },
19
19
  {
20
- name: 'chart-changelog-version',
20
+ name: 'chart-changelog-version', prepare: true,
21
21
  requires: ['chartFilePath'],
22
22
  run: (ctx) => requireChartChangelogVersion(path.dirname(ctx.chartFilePath))
23
23
  },
@@ -3,7 +3,7 @@ const path = require('path');
3
3
  const { execSync } = require('child_process');
4
4
  const { logSuccess, logError, colors } = require('../common/utils');
5
5
  const { runCommand } = require('../cli/command-executor');
6
- const { requireGitRepo, requireCurrentBranchUpToDateWithRemote, requireSingleValuesYaml, requireIngressPathSections, requireSourcePathDirectory, requireNextProject, requireBuildCommandSupport } = require('../preflight');
6
+ const { requireGitRepo, requireCurrentBranchUpToDateWithRemote, requireSingleValuesYaml, readIngressPathSections, requireIngressPathSections, requireSourcePathDirectory, readSourceProject, requireNextProject, requireBuildCommandSupport } = require('../preflight');
7
7
  const { normalizeList, normalizeToBeList, collectRoutesFromFilesystem, collectRoutesFromBuildArtifacts } = require('./routes');
8
8
  const FIXED_ASSET_PATHS = [
9
9
  '/_next/image$',
@@ -96,10 +96,10 @@ function chartVerify(sourcePath) {
96
96
  checks: [
97
97
  { name: 'git-repo', run: requireGitRepo },
98
98
  { name: 'branch-up-to-date', run: requireCurrentBranchUpToDateWithRemote },
99
- { name: 'single-values-yaml', run: requireSingleValuesYaml },
100
- { name: 'values-ingress-sections', requires: ['valuesYamlPath'], run: (ctx) => requireIngressPathSections(ctx.valuesYamlPath) },
101
- { name: 'source-path-directory', run: () => requireSourcePathDirectory(sourcePath) },
102
- { name: 'next-project', requires: ['sourcePath'], run: (ctx) => requireNextProject(ctx.sourcePath) },
99
+ { name: 'single-values-yaml', prepare: true, run: requireSingleValuesYaml },
100
+ { name: 'values-ingress-sections', prepare: (ctx) => readIngressPathSections(ctx.valuesYamlPath), requires: ['valuesYamlPath'], run: (ctx) => requireIngressPathSections(ctx.valuesYamlPath) },
101
+ { name: 'source-path-directory', prepare: true, run: () => requireSourcePathDirectory(sourcePath) },
102
+ { name: 'next-project', prepare: (ctx) => readSourceProject(ctx.sourcePath), requires: ['sourcePath'], run: (ctx) => requireNextProject(ctx.sourcePath) },
103
103
  { name: 'build-command-support', requires: ['sourcePath'], run: (ctx) => requireBuildCommandSupport(ctx.sourcePath) }
104
104
  ],
105
105
  steps: [
@@ -59,7 +59,14 @@ function describePreflight(checkName, context) {
59
59
  }
60
60
 
61
61
  function withCommandOptions(options, action) {
62
- return commandContext.run({ dry: Boolean(options?.dry), silence: Boolean(options?.silence) }, action);
62
+ if (options?.dry && options?.verify === false) {
63
+ throw new Error('--dry и --no-verify нельзя использовать вместе.');
64
+ }
65
+ return commandContext.run({
66
+ dry: Boolean(options?.dry),
67
+ silence: Boolean(options?.silence),
68
+ verify: options?.verify !== false
69
+ }, action);
63
70
  }
64
71
 
65
72
  function withDryRun(dry, action) {
@@ -70,18 +77,22 @@ function isDryRun() {
70
77
  return Boolean(commandContext.getStore()?.dry);
71
78
  }
72
79
 
80
+ function isVerificationSkipped() {
81
+ return commandContext.getStore()?.verify === false;
82
+ }
83
+
73
84
  function reportDryRun(_name) {
74
85
  logSuccess('✅', 'Все предпроверки пройдены, проблем нет. Действия не выполнялись.');
75
86
  }
76
87
 
77
88
  function reportPreflight(_commandName, checkName, description = PREFLIGHT_DESCRIPTIONS[checkName] || checkName) {
78
- if (!commandContext.getStore()?.silence) {
89
+ if (!isVerificationSkipped() && !commandContext.getStore()?.silence) {
79
90
  logSuccess('🔎', 'Проверено (%s): %s.', checkName, description);
80
91
  }
81
92
  }
82
93
 
83
94
  function reportNoPreflights(_name) {
84
- logSuccess('🔎', 'Предпроверок нет.');
95
+ if (!isVerificationSkipped()) logSuccess('🔎', 'Предпроверок нет.');
85
96
  }
86
97
 
87
98
  function normalizeCheckResult(result, fallbackReason) {
@@ -120,6 +131,24 @@ async function runCommand(spec) {
120
131
  let failedPreflights = 0;
121
132
  const results = new Map();
122
133
  for (const check of checks) {
134
+ // Some checks also read inputs needed by the action. In no-verify mode
135
+ // only their explicit preparation runs, without policy checks or reports.
136
+ if (isVerificationSkipped()) {
137
+ if (!check.prepare) continue;
138
+ const prepare = check.prepare === true ? check.run : check.prepare;
139
+ let result;
140
+ try {
141
+ result = normalizeCheckResult(await prepare(context), 'Не удалось получить данные команды.');
142
+ } catch (error) {
143
+ result = { ok: false, reason: error?.message || String(error) };
144
+ }
145
+ if (!result.ok) {
146
+ logError('❌', 'Не удалось подготовить команду (%s): %s', check.name, result.reason);
147
+ return false;
148
+ }
149
+ if (result.data) Object.assign(context, result.data);
150
+ continue;
151
+ }
123
152
  const checkName = check.name || 'unknown-check';
124
153
  const failedDependencies = (check.dependsOn || []).filter((dependency) => results.get(dependency) !== true);
125
154
  const missingInputs = (check.requires || []).filter((key) => context[key] === undefined || context[key] === null);
@@ -178,4 +207,4 @@ async function runCommand(spec) {
178
207
  return true;
179
208
  }
180
209
 
181
- module.exports = { runCommand, withCommandOptions, withDryRun, isDryRun, reportDryRun, reportPreflight, reportNoPreflights };
210
+ module.exports = { runCommand, withCommandOptions, withDryRun, isDryRun, isVerificationSkipped, reportDryRun, reportPreflight, reportNoPreflights };
@@ -20,6 +20,7 @@ program
20
20
  program
21
21
  .command("upgrade")
22
22
  .description("Обновить Spectrum CLI через npm install -g spectrum-cli")
23
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
23
24
  .option("-d, --dry", "Запустить только предпроверки")
24
25
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
25
26
  .action((command) => runAction(upgrade, command));
@@ -33,6 +34,7 @@ nextjsCmd
33
34
  .command("licenses")
34
35
  .description("Сохранить LICENSE встроенных пакетов в standalone")
35
36
  .allowExcessArguments(false)
37
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
36
38
  .option("-d, --dry", "Запустить только предпроверки")
37
39
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
38
40
  .action((command) => runAutomationAction(nextjs.licenses, command));
@@ -50,6 +52,7 @@ catalogCmd
50
52
  .command("resolve")
51
53
  .description("Найти проверенные OCI-каталоги для образа приложения")
52
54
  .allowExcessArguments(false)
55
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
53
56
  .option("-d, --dry", "Запустить только предпроверки")
54
57
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
55
58
  .action((command) => runAutomationAction(sbom.resolve, command));
@@ -58,6 +61,7 @@ catalogCmd
58
61
  .command("populate")
59
62
  .description("Построить и опубликовать каталог из официальных исходников")
60
63
  .allowExcessArguments(false)
64
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
61
65
  .option("-d, --dry", "Запустить только предпроверки")
62
66
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
63
67
  .action((command) => runAutomationAction(sbom.populate, command));
@@ -71,6 +75,7 @@ securityCmd
71
75
  .command("scan")
72
76
  .description("Проверить production npm-зависимости и runtime-образ")
73
77
  .allowExcessArguments(false)
78
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
74
79
  .option("-d, --dry", "Запустить только предпроверки")
75
80
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
76
81
  .action((command) => runAutomationAction(security.scan, command));
@@ -79,6 +84,7 @@ securityCmd
79
84
  .command("track")
80
85
  .description("Создать или найти YouTrack-задачи в центральном pipeline")
81
86
  .allowExcessArguments(false)
87
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
82
88
  .option("-d, --dry", "Запустить только предпроверки")
83
89
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
84
90
  .action((command) => runAutomationAction(security.track, command));
@@ -87,6 +93,7 @@ securityCmd
87
93
  .command("reconcile")
88
94
  .description("Дождаться центрального tracker и получить номера задач")
89
95
  .allowExcessArguments(false)
96
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
90
97
  .option("-d, --dry", "Запустить только предпроверки")
91
98
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
92
99
  .action((command) => runAutomationAction(security.reconcile, command));
@@ -95,6 +102,7 @@ securityCmd
95
102
  .command("repair")
96
103
  .description("Исправить, проверить и создать отдельный MR на проектную задачу")
97
104
  .allowExcessArguments(false)
105
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
98
106
  .option("-d, --dry", "Запустить только предпроверки")
99
107
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
100
108
  .action((command) => runAutomationAction(security.repair, command));
@@ -108,6 +116,7 @@ releaseCmd
108
116
  .command("start")
109
117
  .description("Схлопнуть fragments и атомарно отправить release commit в dev и main/master")
110
118
  .option("--stable", "Явно перейти с 0.x на первый стабильный релиз 1.0.0")
119
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
111
120
  .option("-d, --dry", "Запустить только предпроверки")
112
121
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
113
122
  .action((command) => runAction(release.releaseStart, command));
@@ -115,6 +124,7 @@ releaseCmd
115
124
  releaseCmd
116
125
  .command("close")
117
126
  .description("Свести стабильный релиз из main/master в dev")
127
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
118
128
  .option("-d, --dry", "Запустить только предпроверки")
119
129
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
120
130
  .action((command) => runAction(release.releaseClose, command));
@@ -122,6 +132,7 @@ releaseCmd
122
132
  releaseCmd
123
133
  .command("deploy")
124
134
  .description("Создать и отправить только стабильный тег release/X.Y.Z")
135
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
125
136
  .option("-d, --dry", "Запустить только предпроверки")
126
137
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
127
138
  .action((command) => runAction(git.gitCreateTagAndPush, command));
@@ -130,16 +141,19 @@ const hotfixCmd = program.command("hotfix").description("Изолированн
130
141
 
131
142
  hotfixCmd.command("start")
132
143
  .description("Подготовить 🩹 CHANGELOG на hotfix/* или main/master, без commit, push и merge")
144
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
133
145
  .option("-d, --dry", "Запустить только предпроверки")
134
146
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
135
147
  .action((command) => runAction(hotfix.hotfixStart, command));
136
148
  hotfixCmd.command("deploy")
137
149
  .description("После merge хотфикса отправить stable-тег с main/master")
150
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
138
151
  .option("-d, --dry", "Запустить только предпроверки")
139
152
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
140
153
  .action((command) => runAction(hotfix.hotfixDeploy, command));
141
154
  hotfixCmd.command("close")
142
155
  .description("После stable pipeline свести production в dev")
156
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
143
157
  .option("-d, --dry", "Запустить только предпроверки")
144
158
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
145
159
  .action((command) => runAction(hotfix.hotfixClose, command));
@@ -152,6 +166,7 @@ const changelogCmd = program
152
166
  changelogCmd
153
167
  .command("append <message>")
154
168
  .description("Создать changelog fragment с номером задачи из ветки")
169
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
155
170
  .option("-d, --dry", "Запустить только предпроверки")
156
171
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
157
172
  .action((message, command) => runAction(() => changelog.changelogAppend(message), command));
@@ -159,6 +174,7 @@ changelogCmd
159
174
  changelogCmd
160
175
  .command("check")
161
176
  .description("Проверить CHANGELOG.md и changelog fragments")
177
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
162
178
  .option("-d, --dry", "Запустить только предпроверки")
163
179
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
164
180
  .action((command) => runAction(changelog.changelogCheck, command));
@@ -166,6 +182,7 @@ changelogCmd
166
182
  changelogCmd
167
183
  .command("write")
168
184
  .description("Привести заголовки CHANGELOG.md к формату релиза и запустить Prettier")
185
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
169
186
  .option("-d, --dry", "Запустить только предпроверки")
170
187
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
171
188
  .action((command) => runAction(changelog.changelogWrite, command));
@@ -180,7 +197,8 @@ chartCmd
180
197
  .description("Создать и отправить chart-тег по верхней версии CHANGELOG.md чарта")
181
198
  .allowExcessArguments(false)
182
199
  .option("-f, --force", "Разрешить версию не больше последней версии тега на origin")
183
- .option("-n, --no-wait", "Не ждать публикации версии чарта в Helm-registry")
200
+ .option("--no-wait", "Не ждать публикации версии чарта в Helm-registry")
201
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
184
202
  .option("-d, --dry", "Запустить только предпроверки")
185
203
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
186
204
  .action((command) => runAction(chart.chartStart, command));
@@ -188,6 +206,7 @@ chartCmd
188
206
  chartCmd
189
207
  .command("verify <source_path>")
190
208
  .description("Проверить пути ingress chart относительно исходников Next.js")
209
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
191
210
  .option("-d, --dry", "Запустить только предпроверки")
192
211
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
193
212
  .action((sourcePath, command) => runAction(() => chart.chartVerify(sourcePath), command));
@@ -196,6 +215,7 @@ chartCmd
196
215
  .command("deploy")
197
216
  .description("Задеплоить последнюю версию chart в файлы helmrelease")
198
217
  .option("-i, --instances <names>", "Список инстансов через запятую (по умолчанию все)")
218
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
199
219
  .option("-d, --dry", "Запустить только предпроверки")
200
220
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
201
221
  .action((command) => runAction(chart.chartDeploy, command));
@@ -207,6 +227,7 @@ const tokenCmd = program
207
227
  tokenCmd
208
228
  .command("rotate")
209
229
  .description("Пролить SC_BOT_PAT в CI variables через owner PAT")
230
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
210
231
  .option("-d, --dry", "Запустить только предпроверки")
211
232
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
212
233
  .action((command) => runAction(token.tokenRotate, command));
package/src/cli/help.js CHANGED
@@ -23,7 +23,9 @@ program.configureHelp({
23
23
  if (commands.length > 0) {
24
24
  output += "Команды:\n";
25
25
  commands.forEach((cmd) => {
26
- const nameAndArgs = cmd.name() + cmd.usage().replace(/^[^\s]+\s*/, " ");
26
+ const nameAndArgs = helper.subcommandTerm
27
+ ? helper.subcommandTerm(cmd)
28
+ : cmd.name() + cmd.usage().replace(/^[^\s]+\s*/, " ");
27
29
  const aliases = cmd.aliases();
28
30
  const description = cmd.description();
29
31
 
@@ -50,6 +52,7 @@ program.configureHelp({
50
52
  return output;
51
53
  },
52
54
  });
55
+ for (const command of program.commands || []) configureHelp(command);
53
56
  }
54
57
 
55
58
  module.exports = { configureHelp };
@@ -7,7 +7,7 @@ function commandOptions(command) {
7
7
 
8
8
  async function runAction(action, command) {
9
9
  const options = commandOptions(command);
10
- if (!options.dry) {
10
+ if (!options.dry && options.verify !== false) {
11
11
  try {
12
12
  await checkForUpdates();
13
13
  } catch {
package/src/git/index.js CHANGED
@@ -86,7 +86,7 @@ function gitCreateTagAndPush() {
86
86
  { name: 'clean-working-tree', run: requireCleanWorkingTree },
87
87
  { name: 'on-main-branch', run: requireOnMainBranch },
88
88
  { name: 'branch-up-to-date', run: requireCurrentBranchUpToDateWithRemote },
89
- { name: 'changelog-release-version', run: requireChangelogReleaseVersion },
89
+ { name: 'changelog-release-version', prepare: true, run: requireChangelogReleaseVersion },
90
90
  {
91
91
  name: 'tag-missing',
92
92
  requires: ['version'],
@@ -1,15 +1,19 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const preflight = require('../preflight');
4
- const { logError } = require('../common/utils');
5
- const { reportPreflight } = require('../cli/command-executor');
4
+ const { logError, getMainBranch, getDevelopBranch } = require('../common/utils');
5
+ const { reportPreflight, isVerificationSkipped } = require('../cli/command-executor');
6
6
  const { upVersion, compareVersions } = require('../common/version');
7
7
  const { STABLE_TAG_PATTERNS, parseStableTag, stableTagNames } = require('../release/stable-tags');
8
8
  const { git, exists, ancestor, readAt } = require('./git');
9
9
  function preflightReport(commandName) {
10
10
  let failures = 0;
11
11
  return {
12
- check(name, action, description, unavailableReason) {
12
+ check(name, action, description, unavailableReason, prepare = false) {
13
+ if (isVerificationSkipped()) {
14
+ // Reading/assembling action inputs is still required.
15
+ return { ok: true, value: prepare ? action() : undefined };
16
+ }
13
17
  if (unavailableReason) {
14
18
  logError('❌', 'Предпроверка не выполнена (%s): %s', name, unavailableReason);
15
19
  failures += 1;
@@ -30,6 +34,20 @@ function preflightReport(commandName) {
30
34
  }
31
35
 
32
36
  function context(mode, checks) {
37
+ if (isVerificationSkipped()) {
38
+ const mainBranch = getMainBranch();
39
+ const head = git('rev-parse', 'HEAD').trim();
40
+ if (mode !== 'start') {
41
+ const version = preflight.getChangelogReleaseVersion();
42
+ if (!version) throw new Error('Не удалось прочитать версию из CHANGELOG.md.');
43
+ return { repositoryRootOk: true, mainBranch, head, stable: version, target: version };
44
+ }
45
+ const result = preflight.readLocalStableVersion();
46
+ if (!result.ok) throw new Error(result.reason);
47
+ const stable = result.data.stableVersion;
48
+ return { repositoryRootOk: true, mainBranch, head, stable,
49
+ productionText: fs.readFileSync('CHANGELOG.md', 'utf8'), target: upVersion(stable, 'patch') };
50
+ }
33
51
  const root = checks.check('repository-root', () => {
34
52
  const root = git('rev-parse', '--show-toplevel').trim();
35
53
  if (path.relative(fs.realpathSync(root), fs.realpathSync(process.cwd())) !== '') {
@@ -122,6 +140,11 @@ function assertOriginUnchanged(ctx) {
122
140
  }
123
141
  }
124
142
 
143
+ function integrationContext() {
144
+ const dev = getDevelopBranch();
145
+ return { dev, devRef: `refs/remotes/origin/${dev}` };
146
+ }
147
+
125
148
  function currentFragments() {
126
149
  if (!preflight.findChangelogFragmentFiles().length) return [];
127
150
  const result = preflight.requireChangelogFragments();
@@ -141,4 +164,4 @@ function run(action) {
141
164
  }
142
165
  }
143
166
 
144
- module.exports = { preflightReport, context, assertTagAvailable, assertOriginUnchanged, currentFragments, run };
167
+ module.exports = { preflightReport, context, integrationContext, assertTagAvailable, assertOriginUnchanged, currentFragments, run };
@@ -1,8 +1,8 @@
1
1
  const { logSuccess } = require('../common/utils');
2
- const { isDryRun, reportDryRun } = require('../cli/command-executor');
2
+ const { isDryRun, isVerificationSkipped, reportDryRun } = require('../cli/command-executor');
3
3
  const { parseChangelog } = require('../changelog/hotfix');
4
4
  const { git, exists, ancestor } = require('./git');
5
- const { preflightReport, context, assertOriginUnchanged, run } = require('./checks');
5
+ const { preflightReport, context, integrationContext, assertOriginUnchanged, run } = require('./checks');
6
6
  function hotfixClose() {
7
7
  return run(() => {
8
8
  const checks = preflightReport('hotfix close');
@@ -15,7 +15,7 @@ function hotfixClose() {
15
15
  }
16
16
  }, `опубликованный тег ${tag} указывает на текущий commit`,
17
17
  ctx.stable && ctx.productionText && ctx.remoteTags && ctx.head ? null : 'не определены stable-тег или production commit');
18
- const integration = checks.check('integration-history', () => {
18
+ const integration = isVerificationSkipped() ? { value: integrationContext() } : checks.check('integration-history', () => {
19
19
  const dev = ['develop', 'dev'].find((name) => exists(`refs/remotes/origin/${name}`));
20
20
  if (!dev) throw new Error('Не найдена integration-ветка origin/dev или origin/develop.');
21
21
  const devRef = `refs/remotes/origin/${dev}`;
@@ -21,13 +21,13 @@ function hotfixStart() {
21
21
  ctx.target ? null : 'не определена целевая версия хотфикса');
22
22
  const fragmentsResult = checks.check('changelog-fragments', currentFragments,
23
23
  (value) => `проверены ${value.length} patch fragments`,
24
- ctx.repositoryRootOk ? null : 'не пройдена проверка корня репозитория');
24
+ ctx.repositoryRootOk ? null : 'не пройдена проверка корня репозитория', true);
25
25
  let original;
26
26
  const changelog = checks.check('changelog-state', () => {
27
27
  original = fs.readFileSync(CHANGELOG_FILE, 'utf8');
28
28
  return prepareChangelog({ text: original, ...ctx, fragments: fragmentsResult.value });
29
29
  }, `CHANGELOG.md допускает подготовку хотфикса ${ctx.target}`,
30
- ctx.target && fragmentsResult.ok ? null : 'не определена версия хотфикса или не проверены fragments');
30
+ ctx.target && fragmentsResult.ok ? null : 'не определена версия хотфикса или не проверены fragments', true);
31
31
  checks.check('origin-unchanged', () => assertOriginUnchanged(ctx),
32
32
  'production-ветка на origin не изменилась во время проверки',
33
33
  ctx.mainBranch && ctx.mainSha ? null : 'не проверена история production');
@@ -167,7 +167,7 @@ function extractYamlList(content, sectionName) {
167
167
  return values;
168
168
  }
169
169
 
170
- function requireIngressPathSections(valuesYamlPath) {
170
+ function readIngressPathSections(valuesYamlPath) {
171
171
  if (!valuesYamlPath || !fs.existsSync(valuesYamlPath)) {
172
172
  return fail(`Обязательный файл "${valuesYamlPath}" не существует.`);
173
173
  }
@@ -183,6 +183,14 @@ function requireIngressPathSections(valuesYamlPath) {
183
183
  const pages = extractYamlList(content, 'pages');
184
184
  const assets = extractYamlList(content, 'assets');
185
185
 
186
+ return ok({ valuesIngressPaths: { api, pages, assets } });
187
+ }
188
+
189
+ function requireIngressPathSections(valuesYamlPath) {
190
+ const result = readIngressPathSections(valuesYamlPath);
191
+ if (!result.ok) return result;
192
+ const { api, pages, assets } = result.data.valuesIngressPaths;
193
+
186
194
  if (api.length === 0) {
187
195
  return fail(`Отсутствует или пуст ingress.paths.api в "${valuesYamlPath}".`);
188
196
  }
@@ -193,9 +201,7 @@ function requireIngressPathSections(valuesYamlPath) {
193
201
  return fail(`Отсутствует или пуст ingress.paths.assets в "${valuesYamlPath}".`);
194
202
  }
195
203
 
196
- return ok({
197
- valuesIngressPaths: { api, pages, assets }
198
- });
204
+ return result;
199
205
  }
200
206
 
201
- module.exports = { findValuesYamlFiles, findHelmReleaseFiles, requireHelmReleaseFiles, requireSingleValuesYaml, extractYamlList, requireIngressPathSections };
207
+ module.exports = { findValuesYamlFiles, findHelmReleaseFiles, requireHelmReleaseFiles, requireSingleValuesYaml, extractYamlList, readIngressPathSections, requireIngressPathSections };
@@ -28,6 +28,17 @@ function hasNextConfig(sourcePath) {
28
28
  return files.some((fileName) => fs.existsSync(path.join(sourcePath, fileName)));
29
29
  }
30
30
 
31
+ function readSourceProject(sourcePath) {
32
+ return ok({
33
+ sourcePackageJsonPath: path.join(sourcePath, 'package.json'),
34
+ sourcePackageManager: fs.existsSync(path.join(sourcePath, 'yarn.lock'))
35
+ ? 'yarn'
36
+ : fs.existsSync(path.join(sourcePath, 'bun.lockb')) ? 'bun' : 'npm',
37
+ sourceHasLockfile: ['package-lock.json', 'yarn.lock', 'bun.lockb']
38
+ .some((file) => fs.existsSync(path.join(sourcePath, file)))
39
+ });
40
+ }
41
+
31
42
  function requireNextProject(sourcePath) {
32
43
  if (!sourcePath) {
33
44
  return fail('Требуется путь к исходникам.');
@@ -53,19 +64,7 @@ function requireNextProject(sourcePath) {
53
64
  return fail(`"${sourcePath}" не похож на проект Next.js.`);
54
65
  }
55
66
 
56
- return ok({
57
- sourcePackageJsonPath: pkgPath,
58
- sourcePackageManager: fs.existsSync(path.join(sourcePath, 'yarn.lock'))
59
- ? 'yarn'
60
- : fs.existsSync(path.join(sourcePath, 'bun.lockb'))
61
- ? 'bun'
62
- : 'npm',
63
- sourceHasLockfile: Boolean(
64
- fs.existsSync(path.join(sourcePath, 'package-lock.json')) ||
65
- fs.existsSync(path.join(sourcePath, 'yarn.lock')) ||
66
- fs.existsSync(path.join(sourcePath, 'bun.lockb'))
67
- )
68
- });
67
+ return readSourceProject(sourcePath);
69
68
  }
70
69
 
71
70
  function requireBuildCommandSupport(sourcePath) {
@@ -92,4 +91,4 @@ function requireBuildCommandSupport(sourcePath) {
92
91
  return ok({ sourceBuildScript: pkg.scripts.build });
93
92
  }
94
93
 
95
- module.exports = { requireSourcePathDirectory, requireNextProject, requireBuildCommandSupport };
94
+ module.exports = { requireSourcePathDirectory, readSourceProject, requireNextProject, requireBuildCommandSupport };
@@ -65,6 +65,14 @@ function requireLatestStableVersion() {
65
65
  return ok({ stableVersion });
66
66
  }
67
67
 
68
+ // Read the cached baseline for execution without fetching or verifying origin.
69
+ function readLocalStableVersion() {
70
+ const names = execSilent(`git tag --merged origin/${getMainBranch()} --list "release/*" "hotfix/*" "v*"`);
71
+ const versions = String(names || '').split('\n').map((tag) => parseStableTag(tag.trim())?.version)
72
+ .filter(Boolean).sort(compareStableVersions);
73
+ return versions.length ? ok({ stableVersion: versions.at(-1) }) : fail('Не удалось прочитать локальную stable-версию.');
74
+ }
75
+
68
76
  function requireStableTagAtHead(version) {
69
77
  if (!STABLE_SEMVER_PATTERN.test(String(version || ''))) return fail('Требуется stable-версия X.Y.Z.');
70
78
  const head = execSilent('git rev-parse HEAD');
@@ -136,4 +144,4 @@ function requireReleaseVersionAvailable(version) {
136
144
  return ok();
137
145
  }
138
146
 
139
- module.exports = { requireNoPendingRelease, requireLatestStableVersion, requireStableTagAtHead, requireYouTrackTask, requireTagMissing, requireReleaseVersionAvailable };
147
+ module.exports = { requireNoPendingRelease, requireLatestStableVersion, readLocalStableVersion, requireStableTagAtHead, requireYouTrackTask, requireTagMissing, requireReleaseVersionAvailable };
@@ -1,6 +1,8 @@
1
1
  const { changelogBuildRelease, changelogRemoveFragments } = require('../changelog');
2
- const { requireReleaseState, requireReleaseFragments, appendOpenRelease } = require('../changelog/open-release');
3
- const { runCommand } = require('../cli/command-executor');
2
+ const { requireReleaseState, readLocalReleaseState, requireReleaseFragments, appendOpenRelease } = require('../changelog/open-release');
3
+ const { runCommand, isVerificationSkipped } = require('../cli/command-executor');
4
+ const { getMainBranch, getDevelopBranch } = require('../common/utils');
5
+ const { readLocalStableVersion } = require('../preflight/tags');
4
6
  const { requireGitRepo, requireCleanWorkingTree, requireCurrentBranchUpToDateWithRemote, requireMainAndDevBranches, requireOnDevBranch, requireDevContainsRemoteMain, requireReleaseVersionAvailable, requireLatestStableVersion, requireFileExists, requireChangelogFormatted } = require('../preflight');
5
7
  const { CHANGELOG_FILE } = require('../changelog/config');
6
8
  const { detectBumpType, resolveReleaseVersion } = require('./version');
@@ -12,16 +14,17 @@ function releaseStart(options = {}) {
12
14
  { name: 'git-repo', run: requireGitRepo },
13
15
  { name: 'clean-working-tree', run: requireCleanWorkingTree },
14
16
  { name: 'branch-up-to-date', run: requireCurrentBranchUpToDateWithRemote },
15
- { name: 'main-and-dev-branches', run: requireMainAndDevBranches },
17
+ { name: 'main-and-dev-branches', run: requireMainAndDevBranches,
18
+ prepare: () => ({ ok: true, data: { mainBranch: getMainBranch(), devBranch: getDevelopBranch() } }) },
16
19
  { name: 'on-dev-branch', run: requireOnDevBranch },
17
20
  { name: 'dev-contains-main', run: requireDevContainsRemoteMain },
18
- { name: 'stable-version', run: requireLatestStableVersion },
21
+ { name: 'stable-version', prepare: readLocalStableVersion, run: requireLatestStableVersion },
19
22
  { name: 'changelog-exists', run: () => requireFileExists(CHANGELOG_FILE) },
20
- { name: 'release-state', requires: ['mainBranch', 'stableVersion'], run: requireReleaseState },
23
+ { name: 'release-state', prepare: readLocalReleaseState, requires: ['mainBranch', 'stableVersion'], run: requireReleaseState },
21
24
  { name: 'changelog-prettier-check', run: requireChangelogFormatted },
22
- { name: 'changelog-fragments', run: requireReleaseFragments },
25
+ { name: 'changelog-fragments', prepare: true, run: requireReleaseFragments },
23
26
  {
24
- name: 'detect-bump-type',
27
+ name: 'detect-bump-type', prepare: true,
25
28
  requires: ['changelogFragments', 'stableVersion'],
26
29
  run: (ctx) => {
27
30
  if (options.stable && (!ctx.stableVersion.startsWith('0.') ||
@@ -35,8 +38,10 @@ function releaseStart(options = {}) {
35
38
  return { ok: false, reason: 'Не удалось вычислить release-версию от последнего стабильного тега.' };
36
39
  }
37
40
 
38
- const versionCheck = requireReleaseVersionAvailable(resolved.newVersion);
39
- if (!versionCheck.ok) return versionCheck;
41
+ if (!isVerificationSkipped()) {
42
+ const versionCheck = requireReleaseVersionAvailable(resolved.newVersion);
43
+ if (!versionCheck.ok) return versionCheck;
44
+ }
40
45
  return { ok: true, data: resolved };
41
46
  }
42
47
  }
@@ -1,6 +1,6 @@
1
1
  // Central catalogue writer. Protected pipeline/resource_group remain in CI.
2
2
  const io = require("../automation");
3
- const { isDryRun } = require("../cli/command-executor");
3
+ const { isDryRun, isVerificationSkipped } = require("../cli/command-executor");
4
4
  const { required, checkRegistryAccess } = require("../preflight/automation");
5
5
  const {
6
6
  fs,
@@ -489,7 +489,7 @@ class Publisher {
489
489
  let code = 2;
490
490
  try {
491
491
  const requested = versions(this.env.NEXT_CATALOG_VERSIONS || "[]");
492
- ensure(
492
+ if (!isVerificationSkipped()) ensure(
493
493
  this.env.CI_COMMIT_BRANCH === "main" &&
494
494
  this.env.CI_COMMIT_REF_PROTECTED === "true",
495
495
  "Updater requires protected main",
@@ -526,7 +526,7 @@ class Publisher {
526
526
  ],
527
527
  { input: this.env.NEXUS_PASSWORD },
528
528
  );
529
- report.preflight = await (
529
+ if (!isVerificationSkipped()) report.preflight = await (
530
530
  this.deps.checkRegistryAccess || checkRegistryAccess
531
531
  )(this.repository, {
532
532
  probe: () => this.descriptor(this.tagFor(requested[0])),
@@ -18,7 +18,7 @@ const {
18
18
  checkGitlabAccess,
19
19
  checkExecutables,
20
20
  } = require("../preflight/automation");
21
- const { isDryRun, reportPreflight } = require("../cli/command-executor");
21
+ const { isDryRun, isVerificationSkipped, reportPreflight } = require("../cli/command-executor");
22
22
  const escape = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
23
23
  const pkgName = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+(?![\s\S])/;
24
24
  const tuple = (value) => value.split(".").map(Number);
@@ -506,7 +506,7 @@ class Repairer extends Scanner {
506
506
  "Incomplete scan/tracking; automatic repair is blocked",
507
507
  );
508
508
  const api = this.deps.api || glab(this.env);
509
- report.preflight = {
509
+ if (!isVerificationSkipped()) report.preflight = {
510
510
  ...report.preflight,
511
511
  gitlab_repair: await (this.deps.checkGitlabAccess || checkGitlabAccess)(
512
512
  this.env,
@@ -12,7 +12,7 @@ const { encoded } = require("../sbom/schema");
12
12
  const { glab, projectPath, pages, API } = require("./runtime");
13
13
  const { HIGH } = require("./findings");
14
14
  const { Tracker } = require("./tracker");
15
- const { isDryRun, reportPreflight } = require("../cli/command-executor");
15
+ const { isDryRun, isVerificationSkipped, reportPreflight } = require("../cli/command-executor");
16
16
  const {
17
17
  required,
18
18
  httpsEndpoint,
@@ -25,7 +25,7 @@ async function track({
25
25
  deps = {},
26
26
  dry = isDryRun(),
27
27
  } = {}) {
28
- ensure(
28
+ if (!isVerificationSkipped()) ensure(
29
29
  env.NIGHTLY_EXPECTED_COMPONENT_SHA &&
30
30
  env.NIGHTLY_EXPECTED_COMPONENT_SHA === env.CI_COMMIT_SHA,
31
31
  "Tracker must run the same commit as the included component",
@@ -38,7 +38,7 @@ async function track({
38
38
  ]);
39
39
  httpsEndpoint(env.YOUTRACK_URL, "YOUTRACK_URL");
40
40
  const api = deps.api || glab(env);
41
- await (deps.checkGitlabAccess || checkGitlabAccess)(
41
+ if (!isVerificationSkipped()) await (deps.checkGitlabAccess || checkGitlabAccess)(
42
42
  env,
43
43
  [env.NIGHTLY_CONSUMER_PROJECT],
44
44
  { api },
@@ -46,8 +46,6 @@ async function track({
46
46
  const consumer = projectPath(env.NIGHTLY_CONSUMER_PROJECT),
47
47
  jobId = env.NIGHTLY_CONSUMER_SCAN_JOB;
48
48
  ensure(/^\d+(?![\s\S])/.test(jobId || ""), "Invalid consumer job ID");
49
- const job = await api.get(consumer + "/jobs/" + jobId),
50
- pipeline = await api.get(consumer + "/pipelines/" + job.pipeline.id);
51
49
  const report = json(
52
50
  await api.get(
53
51
  consumer +
@@ -58,32 +56,36 @@ async function track({
58
56
  ),
59
57
  ),
60
58
  cfg = report.config;
61
- ensure(
59
+ if (!isVerificationSkipped()) ensure(
62
60
  report.schemaVersion === 1 &&
63
61
  String(cfg.project_id) === env.NIGHTLY_CONSUMER_PROJECT,
64
62
  "Consumer report identity mismatch",
65
63
  );
66
- ensure(
67
- pipeline.source === "schedule" &&
68
- pipeline.ref === cfg.branch &&
69
- pipeline.sha === cfg.sha &&
70
- String(pipeline.id) === String(cfg.pipeline_id),
71
- "Tracking is restricted to matching scheduled branch pipelines",
72
- );
73
- const branch = await api.get(
74
- consumer + "/repository/branches/" + encoded(cfg.branch),
75
- );
76
- ensure(
77
- branch.protected,
78
- "Nightly automation requires a protected production branch",
79
- );
64
+ if (!isVerificationSkipped()) {
65
+ const job = await api.get(consumer + "/jobs/" + jobId);
66
+ const pipeline = await api.get(consumer + "/pipelines/" + job.pipeline.id);
67
+ ensure(
68
+ pipeline.source === "schedule" &&
69
+ pipeline.ref === cfg.branch &&
70
+ pipeline.sha === cfg.sha &&
71
+ String(pipeline.id) === String(cfg.pipeline_id),
72
+ "Tracking is restricted to matching scheduled branch pipelines",
73
+ );
74
+ const branch = await api.get(
75
+ consumer + "/repository/branches/" + encoded(cfg.branch),
76
+ );
77
+ ensure(
78
+ branch.protected,
79
+ "Nightly automation requires a protected production branch",
80
+ );
81
+ }
80
82
  const base = httpsEndpoint(env.YOUTRACK_URL, "YOUTRACK_URL");
81
83
  const youtrack = new API(base + "/api", {
82
84
  Authorization: "Bearer " + env.YOUTRACK_PRIVATE_TOKEN,
83
85
  });
84
86
  const tracker = deps.tracker || new Tracker(youtrack, base, env);
85
87
  try {
86
- report.preflight = {
88
+ if (!isVerificationSkipped()) report.preflight = {
87
89
  youtrack: await (deps.checkYouTrackAccess || checkYouTrackAccess)(
88
90
  env,
89
91
  cfg,
@@ -137,7 +139,7 @@ async function reconcile({
137
139
  "CI_PROJECT_ID",
138
140
  ]);
139
141
  const api = deps.api || glab(env);
140
- report.preflight = {
142
+ if (!isVerificationSkipped()) report.preflight = {
141
143
  gitlab: await (deps.checkGitlabAccess || checkGitlabAccess)(
142
144
  env,
143
145
  [env.NIGHTLY_TRACKER_PROJECT],
@@ -164,13 +166,15 @@ async function reconcile({
164
166
  );
165
167
  ref = names.at(-1);
166
168
  }
167
- const commit = await api.get(
168
- target + "/repository/commits/" + encoded(ref),
169
- );
170
- ensure(
171
- commit.id === sha,
172
- "Tracker reference does not match the included component",
173
- );
169
+ if (!isVerificationSkipped()) {
170
+ const commit = await api.get(
171
+ target + "/repository/commits/" + encoded(ref),
172
+ );
173
+ ensure(
174
+ commit.id === sha,
175
+ "Tracker reference does not match the included component",
176
+ );
177
+ }
174
178
  reportPreflight(
175
179
  "security reconcile",
176
180
  "tracker-reference",
@@ -144,8 +144,8 @@ function tokenRotate() {
144
144
  return runCommand({
145
145
  name: 'token rotate',
146
146
  checks: [
147
- { name: 'load-config', run: loadOrCreateConfig },
148
- { name: 'ask-tokens', requires: ['tokenTtlMonths', 'bot'], run: askTokens },
147
+ { name: 'load-config', prepare: true, run: loadOrCreateConfig },
148
+ { name: 'ask-tokens', prepare: true, requires: ['tokenTtlMonths', 'bot'], run: askTokens },
149
149
  { name: 'check-access', requires: ['targets', 'origin', 'ownerToken'], run: checkTargetsAccess }
150
150
  ],
151
151
  steps: [