spectrum-cli 5.0.2 → 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.
@@ -0,0 +1,297 @@
1
+ // Shared bounded IO for noninteractive CI commands. Never execute shell strings.
2
+ const fs = require("node:fs");
3
+ const os = require("node:os");
4
+ const path = require("node:path");
5
+ const crypto = require("node:crypto");
6
+ const { spawnSync } = require("node:child_process");
7
+ const { gunzipSync } = require("node:zlib");
8
+ const { isDeepStrictEqual } = require("node:util");
9
+
10
+ const SEMVER =
11
+ /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?![\s\S])/;
12
+ const DIGEST = /^sha256:[a-f0-9]{64}(?![\s\S])/;
13
+ const HEX64 = /^[a-f0-9]{64}(?![\s\S])/;
14
+ const COMMIT = /^[a-f0-9]{40}(?![\s\S])/;
15
+ function ensure(condition, message) {
16
+ if (!condition) throw new Error(message);
17
+ }
18
+ function sha(data, algorithm = "sha256") {
19
+ return crypto.createHash(algorithm).update(data).digest("hex");
20
+ }
21
+ function sorted(value) {
22
+ if (Array.isArray(value)) return value.map(sorted);
23
+ if (value && typeof value === "object" && !Buffer.isBuffer(value))
24
+ return Object.fromEntries(
25
+ Object.keys(value)
26
+ .sort()
27
+ .map((key) => [key, sorted(value[key])]),
28
+ );
29
+ return value;
30
+ }
31
+ function stable(value) {
32
+ return JSON.stringify(sorted(value));
33
+ }
34
+ function json(value) {
35
+ try {
36
+ return JSON.parse(Buffer.isBuffer(value) ? value.toString("utf8") : value);
37
+ } catch {
38
+ throw new Error("Invalid JSON data");
39
+ }
40
+ }
41
+ function read(file) {
42
+ return json(fs.readFileSync(file));
43
+ }
44
+ function save(file, value) {
45
+ fs.mkdirSync(path.dirname(file), { recursive: true });
46
+ const tmp = file + ".tmp";
47
+ fs.writeFileSync(tmp, JSON.stringify(value, null, 2) + "\n");
48
+ fs.renameSync(tmp, file);
49
+ }
50
+ function temp(prefix) {
51
+ return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
52
+ }
53
+ function remove(directory) {
54
+ fs.rmSync(directory, { recursive: true, force: true });
55
+ }
56
+ async function temporary(prefix, action) {
57
+ const dir = temp(prefix);
58
+ try {
59
+ return await action(dir);
60
+ } finally {
61
+ remove(dir);
62
+ }
63
+ }
64
+ function safePath(value, absolute = false) {
65
+ return (
66
+ typeof value === "string" &&
67
+ !!value &&
68
+ !/[\\\x00-\x1f]/.test(value) &&
69
+ value.startsWith("/") === absolute &&
70
+ value
71
+ .replace(/^\//, "")
72
+ .split("/")
73
+ .every((part) => part && part !== "." && part !== "..")
74
+ );
75
+ }
76
+ function repositoryPath(root, relative) {
77
+ ensure(safePath(relative), "Expected a repository-relative path");
78
+ const base = fs.realpathSync(root);
79
+ const target = path.resolve(base, relative);
80
+ let cursor = base;
81
+ for (const part of relative.split("/")) {
82
+ cursor = path.join(cursor, part);
83
+ if (
84
+ fs.existsSync(cursor) ||
85
+ (() => {
86
+ try {
87
+ fs.lstatSync(cursor);
88
+ return true;
89
+ } catch {
90
+ return false;
91
+ }
92
+ })()
93
+ ) {
94
+ const real = fs.realpathSync(cursor);
95
+ ensure(real.startsWith(base + path.sep), "Path leaves repository");
96
+ }
97
+ }
98
+ return target;
99
+ }
100
+ function exec(
101
+ args,
102
+ {
103
+ cwd,
104
+ env = process.env,
105
+ input,
106
+ allowed = [0],
107
+ timeout = 1800000,
108
+ maxBuffer = 32 * 1024 * 1024,
109
+ binary = false,
110
+ label,
111
+ } = {},
112
+ ) {
113
+ ensure(
114
+ Array.isArray(args) &&
115
+ args.length &&
116
+ args.every((arg) => typeof arg === "string" && !arg.includes("\0")),
117
+ "Expected command argv",
118
+ );
119
+ const result = spawnSync(args[0], args.slice(1), {
120
+ cwd,
121
+ env,
122
+ input,
123
+ timeout,
124
+ maxBuffer,
125
+ encoding: binary ? null : "utf8",
126
+ windowsHide: true,
127
+ });
128
+ ensure(
129
+ !result.error && allowed.includes(result.status),
130
+ `${label || path.basename(args[0]) + " " + (args[1] || "")} failed (exit ${result.status})`,
131
+ );
132
+ return {
133
+ stdout: result.stdout,
134
+ stderr: result.stderr,
135
+ status: result.status,
136
+ };
137
+ }
138
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
139
+ async function responseBytes(response, limit) {
140
+ const chunks = [];
141
+ let size = 0;
142
+ for await (const chunk of response.body || []) {
143
+ size += chunk.length;
144
+ if (size > limit) {
145
+ await response.body?.cancel?.().catch(() => {});
146
+ throw new Error("Download limit exceeded");
147
+ }
148
+ chunks.push(Buffer.from(chunk));
149
+ }
150
+ return Buffer.concat(chunks);
151
+ }
152
+ async function request(
153
+ url,
154
+ {
155
+ method = "GET",
156
+ headers = {},
157
+ body,
158
+ limit = 32 * 1024 * 1024,
159
+ timeout = 60000,
160
+ fetchImpl = fetch,
161
+ } = {},
162
+ ) {
163
+ const controller = new AbortController();
164
+ const timer = setTimeout(() => controller.abort(), timeout);
165
+ try {
166
+ const response = await fetchImpl(url, {
167
+ method,
168
+ headers,
169
+ body,
170
+ redirect: "manual",
171
+ signal: controller.signal,
172
+ });
173
+ const bytes = await responseBytes(response, limit);
174
+ return { status: response.status, headers: response.headers, body: bytes };
175
+ } finally {
176
+ clearTimeout(timer);
177
+ }
178
+ }
179
+ // Extract data only. No archive entry is ever written to the filesystem.
180
+ function tarEntries(
181
+ gzip,
182
+ {
183
+ maxUnpacked = 1024 * 1024 * 1024,
184
+ maxFile = 32 * 1024 * 1024,
185
+ maxEntries = 30000,
186
+ } = {},
187
+ ) {
188
+ const bytes = gunzipSync(gzip, {
189
+ maxOutputLength: maxUnpacked + maxEntries * 1024,
190
+ });
191
+ const entries = [];
192
+ let offset = 0;
193
+ let total = 0;
194
+ let pax = {};
195
+ let longName;
196
+ const str = (data) => data.toString("utf8").replace(/\0.*(?![\s\S])/s, "");
197
+ while (offset + 512 <= bytes.length) {
198
+ const header = bytes.subarray(offset, offset + 512);
199
+ offset += 512;
200
+ if (header.every((byte) => byte === 0)) break;
201
+ const sizeText = str(header.subarray(124, 136)).trim();
202
+ ensure(/^[0-7]+(?![\s\S])/.test(sizeText), "Invalid tar entry size");
203
+ const size = parseInt(sizeText, 8);
204
+ const expected = parseInt(str(header.subarray(148, 156)).trim(), 8);
205
+ const sum = header.reduce(
206
+ (sum, b, i) => sum + (i >= 148 && i < 156 ? 32 : b),
207
+ 0,
208
+ );
209
+ ensure(
210
+ expected === sum && size <= maxFile && offset + size <= bytes.length,
211
+ "Invalid or oversized tar entry",
212
+ );
213
+ const data = bytes.subarray(offset, offset + size);
214
+ offset += Math.ceil(size / 512) * 512;
215
+ const type = String.fromCharCode(header[156] || 48);
216
+ if (type === "x") {
217
+ pax = {};
218
+ let pos = 0;
219
+ while (pos < data.length) {
220
+ const space = data.indexOf(32, pos);
221
+ ensure(space > pos, "Invalid PAX record");
222
+ const len = Number(data.subarray(pos, space).toString());
223
+ ensure(
224
+ Number.isInteger(len) &&
225
+ len > space - pos + 1 &&
226
+ pos + len <= data.length &&
227
+ data[pos + len - 1] === 10,
228
+ "Invalid PAX length",
229
+ );
230
+ const row = data.subarray(space + 1, pos + len - 1).toString();
231
+ const split = row.indexOf("=");
232
+ ensure(split > 0, "Invalid PAX value");
233
+ pax[row.slice(0, split)] = row.slice(split + 1);
234
+ pos += len;
235
+ }
236
+ continue;
237
+ }
238
+ if (type === "L") {
239
+ longName = str(data).replace(/\n(?![\s\S])/, "");
240
+ continue;
241
+ }
242
+ const prefix =
243
+ str(header.subarray(257, 263)) === "ustar"
244
+ ? str(header.subarray(345, 500))
245
+ : "";
246
+ const name =
247
+ pax.path ||
248
+ longName ||
249
+ (prefix ? prefix + "/" : "") + str(header.subarray(0, 100));
250
+ ensure(
251
+ pax.size === undefined || Number(pax.size) === size,
252
+ "Unsupported PAX size",
253
+ );
254
+ pax = {};
255
+ longName = undefined;
256
+ ensure(
257
+ type === "0" || type === "5",
258
+ "Links and special archive entries are forbidden",
259
+ );
260
+ total += size;
261
+ ensure(
262
+ total <= maxUnpacked && entries.length < maxEntries,
263
+ "Archive expansion limit exceeded",
264
+ );
265
+ entries.push({
266
+ name: name.replace(/\/(?![\s\S])/, ""),
267
+ directory: type === "5",
268
+ data,
269
+ });
270
+ }
271
+ return entries;
272
+ }
273
+ module.exports = {
274
+ fs,
275
+ path,
276
+ SEMVER,
277
+ DIGEST,
278
+ HEX64,
279
+ COMMIT,
280
+ ensure,
281
+ sha,
282
+ stable,
283
+ sorted,
284
+ json,
285
+ read,
286
+ save,
287
+ temp,
288
+ remove,
289
+ temporary,
290
+ safePath,
291
+ repositoryPath,
292
+ exec,
293
+ sleep,
294
+ request,
295
+ tarEntries,
296
+ equal: isDeepStrictEqual,
297
+ };
@@ -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 };
@@ -4,9 +4,12 @@ const hotfix = require('../hotfix');
4
4
  const changelog = require('../changelog');
5
5
  const chart = require('../chart');
6
6
  const token = require('../token');
7
+ const nextjs = require('../nextjs');
8
+ const sbom = require('../sbom');
9
+ const security = require('../security');
7
10
  const { upgrade } = require('./update-check');
8
11
  const { version: pkgVersion } = require('../../package.json');
9
- const { runAction } = require('./run-action');
12
+ const { runAction, runAutomationAction } = require('./run-action');
10
13
 
11
14
  function registerCommands(program) {
12
15
  program
@@ -17,10 +20,93 @@ program
17
20
  program
18
21
  .command("upgrade")
19
22
  .description("Обновить Spectrum CLI через npm install -g spectrum-cli")
23
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
20
24
  .option("-d, --dry", "Запустить только предпроверки")
21
25
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
22
26
  .action((command) => runAction(upgrade, command));
23
27
 
28
+ // Команды Next.js
29
+ const nextjsCmd = program
30
+ .command("nextjs")
31
+ .description("Обработка Next.js build artifacts");
32
+
33
+ nextjsCmd
34
+ .command("licenses")
35
+ .description("Сохранить LICENSE встроенных пакетов в standalone")
36
+ .allowExcessArguments(false)
37
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
38
+ .option("-d, --dry", "Запустить только предпроверки")
39
+ .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
40
+ .action((command) => runAutomationAction(nextjs.licenses, command));
41
+
42
+ // Команды SBOM
43
+ const sbomCmd = program
44
+ .command("sbom")
45
+ .description("Работа с SBOM");
46
+
47
+ const catalogCmd = sbomCmd
48
+ .command("catalog")
49
+ .description("Каталоги встроенных зависимостей Next.js");
50
+
51
+ catalogCmd
52
+ .command("resolve")
53
+ .description("Найти проверенные OCI-каталоги для образа приложения")
54
+ .allowExcessArguments(false)
55
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
56
+ .option("-d, --dry", "Запустить только предпроверки")
57
+ .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
58
+ .action((command) => runAutomationAction(sbom.resolve, command));
59
+
60
+ catalogCmd
61
+ .command("populate")
62
+ .description("Построить и опубликовать каталог из официальных исходников")
63
+ .allowExcessArguments(false)
64
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
65
+ .option("-d, --dry", "Запустить только предпроверки")
66
+ .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
67
+ .action((command) => runAutomationAction(sbom.populate, command));
68
+
69
+ // Команды security
70
+ const securityCmd = program
71
+ .command("security")
72
+ .description("Ночная проверка и исправление HIGH/CRITICAL");
73
+
74
+ securityCmd
75
+ .command("scan")
76
+ .description("Проверить production npm-зависимости и runtime-образ")
77
+ .allowExcessArguments(false)
78
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
79
+ .option("-d, --dry", "Запустить только предпроверки")
80
+ .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
81
+ .action((command) => runAutomationAction(security.scan, command));
82
+
83
+ securityCmd
84
+ .command("track")
85
+ .description("Создать или найти YouTrack-задачи в центральном pipeline")
86
+ .allowExcessArguments(false)
87
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
88
+ .option("-d, --dry", "Запустить только предпроверки")
89
+ .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
90
+ .action((command) => runAutomationAction(security.track, command));
91
+
92
+ securityCmd
93
+ .command("reconcile")
94
+ .description("Дождаться центрального tracker и получить номера задач")
95
+ .allowExcessArguments(false)
96
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
97
+ .option("-d, --dry", "Запустить только предпроверки")
98
+ .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
99
+ .action((command) => runAutomationAction(security.reconcile, command));
100
+
101
+ securityCmd
102
+ .command("repair")
103
+ .description("Исправить, проверить и создать отдельный MR на проектную задачу")
104
+ .allowExcessArguments(false)
105
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
106
+ .option("-d, --dry", "Запустить только предпроверки")
107
+ .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
108
+ .action((command) => runAutomationAction(security.repair, command));
109
+
24
110
  // Команды релиза
25
111
  const releaseCmd = program
26
112
  .command("release")
@@ -30,6 +116,7 @@ releaseCmd
30
116
  .command("start")
31
117
  .description("Схлопнуть fragments и атомарно отправить release commit в dev и main/master")
32
118
  .option("--stable", "Явно перейти с 0.x на первый стабильный релиз 1.0.0")
119
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
33
120
  .option("-d, --dry", "Запустить только предпроверки")
34
121
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
35
122
  .action((command) => runAction(release.releaseStart, command));
@@ -37,6 +124,7 @@ releaseCmd
37
124
  releaseCmd
38
125
  .command("close")
39
126
  .description("Свести стабильный релиз из main/master в dev")
127
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
40
128
  .option("-d, --dry", "Запустить только предпроверки")
41
129
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
42
130
  .action((command) => runAction(release.releaseClose, command));
@@ -44,6 +132,7 @@ releaseCmd
44
132
  releaseCmd
45
133
  .command("deploy")
46
134
  .description("Создать и отправить только стабильный тег release/X.Y.Z")
135
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
47
136
  .option("-d, --dry", "Запустить только предпроверки")
48
137
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
49
138
  .action((command) => runAction(git.gitCreateTagAndPush, command));
@@ -52,16 +141,19 @@ const hotfixCmd = program.command("hotfix").description("Изолированн
52
141
 
53
142
  hotfixCmd.command("start")
54
143
  .description("Подготовить 🩹 CHANGELOG на hotfix/* или main/master, без commit, push и merge")
144
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
55
145
  .option("-d, --dry", "Запустить только предпроверки")
56
146
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
57
147
  .action((command) => runAction(hotfix.hotfixStart, command));
58
148
  hotfixCmd.command("deploy")
59
149
  .description("После merge хотфикса отправить stable-тег с main/master")
150
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
60
151
  .option("-d, --dry", "Запустить только предпроверки")
61
152
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
62
153
  .action((command) => runAction(hotfix.hotfixDeploy, command));
63
154
  hotfixCmd.command("close")
64
155
  .description("После stable pipeline свести production в dev")
156
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
65
157
  .option("-d, --dry", "Запустить только предпроверки")
66
158
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
67
159
  .action((command) => runAction(hotfix.hotfixClose, command));
@@ -74,6 +166,7 @@ const changelogCmd = program
74
166
  changelogCmd
75
167
  .command("append <message>")
76
168
  .description("Создать changelog fragment с номером задачи из ветки")
169
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
77
170
  .option("-d, --dry", "Запустить только предпроверки")
78
171
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
79
172
  .action((message, command) => runAction(() => changelog.changelogAppend(message), command));
@@ -81,6 +174,7 @@ changelogCmd
81
174
  changelogCmd
82
175
  .command("check")
83
176
  .description("Проверить CHANGELOG.md и changelog fragments")
177
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
84
178
  .option("-d, --dry", "Запустить только предпроверки")
85
179
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
86
180
  .action((command) => runAction(changelog.changelogCheck, command));
@@ -88,6 +182,7 @@ changelogCmd
88
182
  changelogCmd
89
183
  .command("write")
90
184
  .description("Привести заголовки CHANGELOG.md к формату релиза и запустить Prettier")
185
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
91
186
  .option("-d, --dry", "Запустить только предпроверки")
92
187
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
93
188
  .action((command) => runAction(changelog.changelogWrite, command));
@@ -102,7 +197,8 @@ chartCmd
102
197
  .description("Создать и отправить chart-тег по верхней версии CHANGELOG.md чарта")
103
198
  .allowExcessArguments(false)
104
199
  .option("-f, --force", "Разрешить версию не больше последней версии тега на origin")
105
- .option("-n, --no-wait", "Не ждать публикации версии чарта в Helm-registry")
200
+ .option("--no-wait", "Не ждать публикации версии чарта в Helm-registry")
201
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
106
202
  .option("-d, --dry", "Запустить только предпроверки")
107
203
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
108
204
  .action((command) => runAction(chart.chartStart, command));
@@ -110,6 +206,7 @@ chartCmd
110
206
  chartCmd
111
207
  .command("verify <source_path>")
112
208
  .description("Проверить пути ingress chart относительно исходников Next.js")
209
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
113
210
  .option("-d, --dry", "Запустить только предпроверки")
114
211
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
115
212
  .action((sourcePath, command) => runAction(() => chart.chartVerify(sourcePath), command));
@@ -118,6 +215,7 @@ chartCmd
118
215
  .command("deploy")
119
216
  .description("Задеплоить последнюю версию chart в файлы helmrelease")
120
217
  .option("-i, --instances <names>", "Список инстансов через запятую (по умолчанию все)")
218
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
121
219
  .option("-d, --dry", "Запустить только предпроверки")
122
220
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
123
221
  .action((command) => runAction(chart.chartDeploy, command));
@@ -129,6 +227,7 @@ const tokenCmd = program
129
227
  tokenCmd
130
228
  .command("rotate")
131
229
  .description("Пролить SC_BOT_PAT в CI variables через owner PAT")
230
+ .option("-n, --no-verify", "Выполнить команду без предпроверок")
132
231
  .option("-d, --dry", "Запустить только предпроверки")
133
232
  .option("-s, --silence", "Скрыть сообщения об успешно пройденных предпроверках")
134
233
  .action((command) => runAction(token.tokenRotate, command));