spectrum-cli 1.1.1 → 1.2.1
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 +27 -0
- package/README.md +63 -11
- package/index.js +63 -41
- package/package.json +5 -3
- package/src/changelog.js +96 -57
- package/src/chart.js +821 -0
- package/src/command-executor.js +70 -0
- package/src/development.js +11 -11
- package/src/git.js +59 -40
- package/src/preflight.js +556 -0
- package/src/release.js +102 -55
- package/src/utils.js +47 -9
- package/src/version.js +22 -17
package/CHANGELOG.md
CHANGED
|
@@ -35,6 +35,33 @@ _Список правок для обеспечения безопасност
|
|
|
35
35
|
|
|
36
36
|
_Список правок для обеспечения технической поддержки._
|
|
37
37
|
|
|
38
|
+
## 🚀 [1.2.1] - 2026-03-05
|
|
39
|
+
|
|
40
|
+
### 🗑 Removed
|
|
41
|
+
|
|
42
|
+
- Убрана проверка на наличие изменений в гит при выполнение команд связанных с версией.
|
|
43
|
+
|
|
44
|
+
## 🚀 [1.2.0] - 2026-03-03
|
|
45
|
+
|
|
46
|
+
### 🆕 Added
|
|
47
|
+
|
|
48
|
+
- Добавлена команда `spectrum chart create <version>` для создания `helm chart`
|
|
49
|
+
- Изменен формат вывода сообщений в консоли
|
|
50
|
+
- Добавлен вывод прямой ссылки на создание Merge Request при `spectrum release start`
|
|
51
|
+
- Добавлена команда `spectrum chart deploy` для обновления `spec.chart.spec.version` в `helmrelease.yaml` до последнего remote chart-тега с интерактивным подтверждением, автокоммитом и пушем
|
|
52
|
+
|
|
53
|
+
### 🛠 Changed
|
|
54
|
+
|
|
55
|
+
- Добавлены новые preflight-проверки для chart deploy: проверка `origin`, доступности remote и наличия `helmrelease.yaml`
|
|
56
|
+
|
|
57
|
+
### 🔐 Security
|
|
58
|
+
|
|
59
|
+
- Обновлены зависимости для устранения CVE в `minmatch`
|
|
60
|
+
|
|
61
|
+
### 📦 Support
|
|
62
|
+
|
|
63
|
+
- Покрытие тестами 100%
|
|
64
|
+
|
|
38
65
|
## 🚀 [1.1.1] - 2025-10-03
|
|
39
66
|
|
|
40
67
|
### 🛠 Changed
|
package/README.md
CHANGED
|
@@ -46,6 +46,11 @@ spectrum release start # Полный цикл релиза
|
|
|
46
46
|
spectrum release close # Закрыть релиз и смержить в dev
|
|
47
47
|
spectrum release deploy # Деплой релиза (создать тег)
|
|
48
48
|
|
|
49
|
+
# 📈 Теги chart
|
|
50
|
+
spectrum chart create 1.2.3 # Создать и запушить chart-$name-$version
|
|
51
|
+
spectrum chart deploy # Обновить helmrelease.yaml до последнего chart-тега и запушить
|
|
52
|
+
spectrum chart verify ~/repo # Сравнить ingress paths AS IS vs TO BE для Next.js исходников
|
|
53
|
+
|
|
49
54
|
# 📝 Управление changelog
|
|
50
55
|
spectrum changelog append "Сообщение" # Добавить запись в CHANGELOG.md
|
|
51
56
|
```
|
|
@@ -61,6 +66,9 @@ spectrum version up --help
|
|
|
61
66
|
|
|
62
67
|
# Справка по changelog
|
|
63
68
|
spectrum changelog --help
|
|
69
|
+
|
|
70
|
+
# Справка по chart
|
|
71
|
+
spectrum chart --help
|
|
64
72
|
```
|
|
65
73
|
|
|
66
74
|
## ✨ Особенности
|
|
@@ -87,6 +95,7 @@ spectrum-cli/
|
|
|
87
95
|
│ ├── git.js # 📝 Git операции
|
|
88
96
|
│ ├── version.js # 📦 Управление версиями SemVer
|
|
89
97
|
│ ├── changelog.js # 📋 Работа с CHANGELOG.md
|
|
98
|
+
│ ├── chart.js # 📈 Создание и push chart тегов
|
|
90
99
|
│ ├── development.js # ⚡ Dev команды (внутренние)
|
|
91
100
|
│ └── release.js # 🚀 Release процесс
|
|
92
101
|
├── package.json # 📦 Конфигурация проекта
|
|
@@ -95,17 +104,33 @@ spectrum-cli/
|
|
|
95
104
|
└── README.md # 📖 Документация
|
|
96
105
|
```
|
|
97
106
|
|
|
98
|
-
###
|
|
99
|
-
|
|
100
|
-
| Команда
|
|
101
|
-
|
|
|
102
|
-
| `spectrum release start`
|
|
103
|
-
| `spectrum release deploy`
|
|
104
|
-
| `spectrum release close`
|
|
105
|
-
| `spectrum version up major`
|
|
106
|
-
| `spectrum version up minor`
|
|
107
|
-
| `spectrum version up patch`
|
|
108
|
-
| `spectrum changelog append`
|
|
107
|
+
### Команды
|
|
108
|
+
|
|
109
|
+
| Команда | Описание |
|
|
110
|
+
| --------------------------- | ----------------------------------- |
|
|
111
|
+
| `spectrum release start` | Запустить полный цикл релиза |
|
|
112
|
+
| `spectrum release deploy` | Деплой релиза |
|
|
113
|
+
| `spectrum release close` | Закрыть релиз |
|
|
114
|
+
| `spectrum version up major` | Увеличить major версию |
|
|
115
|
+
| `spectrum version up minor` | Увеличить minor версию |
|
|
116
|
+
| `spectrum version up patch` | Увеличить patch версию |
|
|
117
|
+
| `spectrum changelog append` | Добавить запись в changelog |
|
|
118
|
+
| `spectrum chart create` | Создать и запушить chart тег |
|
|
119
|
+
| `spectrum chart deploy` | Обновить chart версию в helmrelease |
|
|
120
|
+
| `spectrum chart verify` | Проверить ingress paths chart |
|
|
121
|
+
|
|
122
|
+
### 🛡️ Preflight-проверки по командам
|
|
123
|
+
|
|
124
|
+
- `spectrum release start`: `git-repo` (внутри git-репозитория), `clean-working-tree` (нет незакоммиченных изменений), `main-and-dev-branches` (есть `origin/main` и `origin/develop`), `package-version` (версия в `package.json` существует и валидный semver), `changelog-exists` (есть `CHANGELOG.md`), `changelog-prettier-check` (`prettier --check CHANGELOG.md`).
|
|
125
|
+
- `spectrum release close`: `git-repo`, `clean-working-tree`, `main-and-dev-branches`.
|
|
126
|
+
- `spectrum release deploy`: `git-repo`, `clean-working-tree`, `on-main-branch` (текущая ветка `main`), `package-version`, `tag-missing` (тега `v<version>` нет локально и на `origin`).
|
|
127
|
+
- `spectrum version up major`: `package-json-exists` (есть `package.json`), `package-version`, `clean-working-tree`.
|
|
128
|
+
- `spectrum version up minor`: `package-json-exists`, `package-version`, `clean-working-tree`.
|
|
129
|
+
- `spectrum version up patch`: `package-json-exists`, `package-version`, `clean-working-tree`.
|
|
130
|
+
- `spectrum changelog append <message>`: `changelog-exists`, `prettier-available` (доступен `prettier` или `npx --yes prettier`), `changelog-prettier-check`.
|
|
131
|
+
- `spectrum chart create <version>`: `git-repo`, `clean-working-tree`, `on-main-branch`, `valid-semver` (переданный `<version>` — semver), `single-chart` (ровно один `charts/<chart-name>/Chart.yaml`), `tag-missing` (тега `chart-<name>-<version>` нет локально и на `origin`).
|
|
132
|
+
- `spectrum chart deploy`: `git-repo`, `clean-working-tree`, `on-main-branch` (текущая ветка `main`), `remote-origin` (настроен `origin`), `remote-reachable` (доступен `origin`), `single-chart`, `helmrelease-files` (найдены `helmrelease.yaml`).
|
|
133
|
+
- `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`.
|
|
109
134
|
|
|
110
135
|
## 🔄 Workflow релиза
|
|
111
136
|
|
|
@@ -118,6 +143,7 @@ spectrum-cli/
|
|
|
118
143
|
5. Добавляет новый блок Unreleased
|
|
119
144
|
6. Коммитит изменения
|
|
120
145
|
7. Пушит release ветку
|
|
146
|
+
8. Выводит прямую ссылку на создание Merge Request
|
|
121
147
|
|
|
122
148
|
### `spectrum release close`
|
|
123
149
|
|
|
@@ -133,6 +159,32 @@ spectrum-cli/
|
|
|
133
159
|
2. Создает тег с текущей версией
|
|
134
160
|
3. Пушит тег в remote
|
|
135
161
|
|
|
162
|
+
### `spectrum chart create <version>`
|
|
163
|
+
|
|
164
|
+
1. Ищет `Chart.yaml` в `charts/<chart-name>/Chart.yaml` и читает поле `name`
|
|
165
|
+
2. Проверяет, что текущая ветка — `main`
|
|
166
|
+
3. Проверяет semver для переданной версии
|
|
167
|
+
4. Собирает тег `chart-<name>-<version>`
|
|
168
|
+
5. Проверяет, что такого тега еще нет
|
|
169
|
+
6. Создает тег и пушит его в `origin`
|
|
170
|
+
|
|
171
|
+
### `spectrum chart deploy`
|
|
172
|
+
|
|
173
|
+
1. Находит последний тег чарта на remote `origin` в формате `chart-<app>-<version>`
|
|
174
|
+
2. Ищет все `helmrelease.yaml` в репозитории
|
|
175
|
+
3. Обновляет `spec.chart.spec.version` до найденной версии и печатает список файлов (`зеленым` обновленные, `серым` без изменений)
|
|
176
|
+
4. Если обновлений нет, завершает выполнение сообщением, что все уже на последней версии
|
|
177
|
+
5. Запрашивает подтверждение публикации через Enter
|
|
178
|
+
6. Делает `git add` обновленных файлов, создает коммит `🚀 Deploy service.` и пушит изменения
|
|
179
|
+
|
|
180
|
+
### `spectrum chart verify <source_path>`
|
|
181
|
+
|
|
182
|
+
1. Находит `charts/**/values.yaml` и читает `ingress.paths.api/pages/assets` как AS IS
|
|
183
|
+
2. Собирает TO BE для Next.js из `.next` артефактов, при необходимости запускает build, затем делает fallback на файловую структуру роутов
|
|
184
|
+
3. Для `assets` использует фиксированный набор regex-путей
|
|
185
|
+
4. Печатает diff только по изменениям: лишние пути (`-`) и недостающие (`+`) с цветовой подсветкой
|
|
186
|
+
5. Завершает команду с ошибкой, если есть расхождения
|
|
187
|
+
|
|
136
188
|
## 📝 Работа с Changelog
|
|
137
189
|
|
|
138
190
|
### `spectrum changelog append "Сообщение"`
|
package/index.js
CHANGED
|
@@ -6,81 +6,103 @@ const git = require("./src/git");
|
|
|
6
6
|
const version = require("./src/version");
|
|
7
7
|
const release = require("./src/release");
|
|
8
8
|
const changelog = require("./src/changelog");
|
|
9
|
+
const chart = require("./src/chart");
|
|
9
10
|
const { version: pkgVersion } = require("./package.json");
|
|
10
11
|
|
|
12
|
+
async function runAction(action, ...args) {
|
|
13
|
+
try {
|
|
14
|
+
const result = await action(...args);
|
|
15
|
+
if (!result) {
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
} catch (error) {
|
|
19
|
+
console.error(`❌ Ошибка: ${error.message}`);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
11
24
|
program
|
|
12
25
|
.name("spectrum")
|
|
13
|
-
.description("🚀 Spectrum CLI
|
|
26
|
+
.description("🚀 Spectrum CLI для процесса разработки")
|
|
14
27
|
.version(pkgVersion, "-v, --version");
|
|
15
28
|
|
|
16
|
-
//
|
|
29
|
+
// Команды релиза
|
|
17
30
|
const releaseCmd = program
|
|
18
31
|
.command("release")
|
|
19
|
-
.description("
|
|
32
|
+
.description("Команды управления релизом");
|
|
20
33
|
|
|
21
34
|
releaseCmd
|
|
22
35
|
.command("start")
|
|
23
|
-
.description("
|
|
24
|
-
.action(() => release.releaseStart
|
|
36
|
+
.description("Запустить полный процесс релиза")
|
|
37
|
+
.action(() => runAction(release.releaseStart));
|
|
25
38
|
|
|
26
39
|
releaseCmd
|
|
27
40
|
.command("close")
|
|
28
|
-
.description("
|
|
29
|
-
.action(() => release.releaseClose
|
|
41
|
+
.description("Закрыть релиз и влить в dev")
|
|
42
|
+
.action(() => runAction(release.releaseClose));
|
|
30
43
|
|
|
31
44
|
releaseCmd
|
|
32
45
|
.command("deploy")
|
|
33
|
-
.description("
|
|
34
|
-
.action(() => git.gitCreateTagAndPush
|
|
46
|
+
.description("Деплой релиза (создать и отправить тег)")
|
|
47
|
+
.action(() => runAction(git.gitCreateTagAndPush));
|
|
35
48
|
|
|
36
|
-
//
|
|
49
|
+
// Команды обновления версии
|
|
37
50
|
const versionCmd = program
|
|
38
51
|
.command("version")
|
|
39
|
-
.description("
|
|
52
|
+
.description("Обновить версию по semantic versioning");
|
|
40
53
|
|
|
41
54
|
const upCmd = versionCmd
|
|
42
55
|
.command("up")
|
|
43
|
-
.description("
|
|
56
|
+
.description("Обновить версию по semantic versioning");
|
|
44
57
|
|
|
45
58
|
upCmd
|
|
46
59
|
.command("major")
|
|
47
|
-
.description("
|
|
48
|
-
.action(() => version.
|
|
60
|
+
.description("Повысить major версию (x.0.0)")
|
|
61
|
+
.action(() => runAction(() => version.setVersion("major")));
|
|
49
62
|
|
|
50
63
|
upCmd
|
|
51
64
|
.command("minor")
|
|
52
|
-
.description("
|
|
53
|
-
.action(() => version.
|
|
65
|
+
.description("Повысить minor версию (x.y.0)")
|
|
66
|
+
.action(() => runAction(() => version.setVersion("minor")));
|
|
54
67
|
|
|
55
68
|
upCmd
|
|
56
69
|
.command("patch")
|
|
57
|
-
.description("
|
|
58
|
-
.action(() => version.
|
|
70
|
+
.description("Повысить patch версию (x.y.z)")
|
|
71
|
+
.action(() => runAction(() => version.setVersion("patch")));
|
|
59
72
|
|
|
60
|
-
//
|
|
73
|
+
// Команды changelog
|
|
61
74
|
const changelogCmd = program
|
|
62
75
|
.command("changelog")
|
|
63
|
-
.description("
|
|
76
|
+
.description("Команды управления changelog");
|
|
64
77
|
|
|
65
78
|
changelogCmd
|
|
66
79
|
.command("append <message>")
|
|
67
|
-
.description("
|
|
68
|
-
.action(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
80
|
+
.description("Добавить запись в changelog с номером задачи из ветки")
|
|
81
|
+
.action((message) => runAction(() => changelog.changelogAppend(message)));
|
|
82
|
+
|
|
83
|
+
// Команды chart
|
|
84
|
+
const chartCmd = program
|
|
85
|
+
.command("chart")
|
|
86
|
+
.description("Команды управления тегами chart");
|
|
87
|
+
|
|
88
|
+
chartCmd
|
|
89
|
+
.command("create <version>")
|
|
90
|
+
.description("Создать и отправить chart-тег (chart-<name>-<version>)")
|
|
91
|
+
.action((version) => runAction(() => chart.chartCreateTag(version)));
|
|
92
|
+
|
|
93
|
+
chartCmd
|
|
94
|
+
.command("verify <source_path>")
|
|
95
|
+
.description("Проверить пути ingress chart относительно исходников Next.js")
|
|
96
|
+
.action((sourcePath) => runAction(() => chart.chartVerify(sourcePath)));
|
|
97
|
+
|
|
98
|
+
chartCmd
|
|
99
|
+
.command("deploy")
|
|
100
|
+
.description("Задеплоить последнюю версию chart в файлы helmrelease")
|
|
101
|
+
.action(() => runAction(chart.chartDeploy));
|
|
76
102
|
|
|
77
|
-
//
|
|
103
|
+
// Переопределяем help, чтобы показать кастомный формат с алиасами
|
|
78
104
|
program.configureHelp({
|
|
79
105
|
formatHelp: (cmd, helper) => {
|
|
80
|
-
const termWidth = helper.padWidth(cmd, helper);
|
|
81
|
-
const helpWidth = helper.helpWidth || 80;
|
|
82
|
-
const itemIndentWidth = 2;
|
|
83
|
-
const itemSeparatorWidth = 2;
|
|
84
106
|
const cmdUsage = helper.commandUsage(cmd);
|
|
85
107
|
const cmdDescription = helper.commandDescription(cmd);
|
|
86
108
|
const options = helper.visibleOptions(cmd);
|
|
@@ -88,19 +110,19 @@ program.configureHelp({
|
|
|
88
110
|
|
|
89
111
|
let output = "";
|
|
90
112
|
|
|
91
|
-
//
|
|
113
|
+
// Использование
|
|
92
114
|
if (cmdUsage) {
|
|
93
|
-
output +=
|
|
115
|
+
output += `Использование: ${cmdUsage}\n\n`;
|
|
94
116
|
}
|
|
95
117
|
|
|
96
|
-
//
|
|
118
|
+
// Описание
|
|
97
119
|
if (cmdDescription) {
|
|
98
120
|
output += `${cmdDescription}\n\n`;
|
|
99
121
|
}
|
|
100
122
|
|
|
101
|
-
//
|
|
123
|
+
// Команды с алиасами
|
|
102
124
|
if (commands.length > 0) {
|
|
103
|
-
output += "
|
|
125
|
+
output += "Команды:\n";
|
|
104
126
|
commands.forEach((cmd) => {
|
|
105
127
|
const nameAndArgs = cmd.name() + cmd.usage().replace(/^[^\s]+\s*/, " ");
|
|
106
128
|
const aliases = cmd.aliases();
|
|
@@ -116,9 +138,9 @@ program.configureHelp({
|
|
|
116
138
|
output += "\n";
|
|
117
139
|
}
|
|
118
140
|
|
|
119
|
-
//
|
|
141
|
+
// Опции
|
|
120
142
|
if (options.length > 0) {
|
|
121
|
-
output += "
|
|
143
|
+
output += "Опции:\n";
|
|
122
144
|
options.forEach((option) => {
|
|
123
145
|
const flags = helper.optionTerm(option);
|
|
124
146
|
const description = helper.optionDescription(option);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spectrum-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "🚀 Modern CLI for development workflow automation - release management, versioning, and changelog generation",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -70,8 +70,10 @@
|
|
|
70
70
|
"jest": {
|
|
71
71
|
"testEnvironment": "node",
|
|
72
72
|
"collectCoverageFrom": [
|
|
73
|
-
"
|
|
74
|
-
"
|
|
73
|
+
"index.js",
|
|
74
|
+
"src/command-executor.js",
|
|
75
|
+
"src/development.js",
|
|
76
|
+
"src/utils.js"
|
|
75
77
|
],
|
|
76
78
|
"testMatch": [
|
|
77
79
|
"**/__tests__/**/*.js",
|
package/src/changelog.js
CHANGED
|
@@ -3,6 +3,8 @@ const fs = require('fs');
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const readline = require('readline');
|
|
5
5
|
const { logSuccess, logError, execCommand, execSilent, getVersion, getCurrentBranch, colors } = require('./utils');
|
|
6
|
+
const { runCommand } = require('./command-executor');
|
|
7
|
+
const { requireGitRepo, requireFileExists, requirePrettierAvailable, requireChangelogFormatted } = require('./preflight');
|
|
6
8
|
|
|
7
9
|
function getPrettierRunner() {
|
|
8
10
|
const npxVersion = execSilent('npx --yes prettier --version');
|
|
@@ -15,7 +17,7 @@ function getPrettierRunner() {
|
|
|
15
17
|
function ensurePrettierAvailable() {
|
|
16
18
|
const runner = getPrettierRunner();
|
|
17
19
|
if (!runner) {
|
|
18
|
-
logError('❌', 'Prettier
|
|
20
|
+
logError('❌', 'Prettier недоступен. Установите его или используйте npx.');
|
|
19
21
|
return null;
|
|
20
22
|
}
|
|
21
23
|
return runner;
|
|
@@ -24,9 +26,9 @@ function ensurePrettierAvailable() {
|
|
|
24
26
|
function checkChangelogWithPrettier() {
|
|
25
27
|
const runner = ensurePrettierAvailable();
|
|
26
28
|
if (!runner) return false;
|
|
27
|
-
const ok = execCommand(`${runner} --check CHANGELOG.md
|
|
29
|
+
const ok = execCommand(`${runner} --check CHANGELOG.md`);
|
|
28
30
|
if (!ok) {
|
|
29
|
-
logError('❌', 'CHANGELOG.md
|
|
31
|
+
logError('❌', 'Файл CHANGELOG.md не прошел проверку Prettier. Исправьте форматирование перед продолжением.');
|
|
30
32
|
return false;
|
|
31
33
|
}
|
|
32
34
|
return true;
|
|
@@ -49,7 +51,7 @@ function changelogChangeHeader() {
|
|
|
49
51
|
);
|
|
50
52
|
|
|
51
53
|
fs.writeFileSync('CHANGELOG.md', updatedChangelog);
|
|
52
|
-
logSuccess('🔄',
|
|
54
|
+
logSuccess('🔄', 'Заголовок обновлен до 🚀 [%s].', currentVersion);
|
|
53
55
|
return true;
|
|
54
56
|
} catch (error) {
|
|
55
57
|
return false;
|
|
@@ -65,7 +67,7 @@ function changelogRemoveEmptyChapters() {
|
|
|
65
67
|
const updatedData = data.replaceAll(/###.*\n\n_.*_\n\n/gm, '');
|
|
66
68
|
fs.writeFileSync('CHANGELOG.md', updatedData);
|
|
67
69
|
|
|
68
|
-
logSuccess('🧹', '
|
|
70
|
+
logSuccess('🧹', 'Пустые разделы удалены.');
|
|
69
71
|
return true;
|
|
70
72
|
} catch (error) {
|
|
71
73
|
return false;
|
|
@@ -91,7 +93,7 @@ function changelogAddUnreleasedBlock() {
|
|
|
91
93
|
);
|
|
92
94
|
|
|
93
95
|
fs.writeFileSync('CHANGELOG.md', updatedChangelog);
|
|
94
|
-
logSuccess('📋', '
|
|
96
|
+
logSuccess('📋', 'Блок unreleased добавлен.');
|
|
95
97
|
return true;
|
|
96
98
|
} catch (error) {
|
|
97
99
|
return false;
|
|
@@ -99,11 +101,11 @@ function changelogAddUnreleasedBlock() {
|
|
|
99
101
|
}
|
|
100
102
|
|
|
101
103
|
function changelogCommit() {
|
|
102
|
-
const addSuccess = execCommand('git add CHANGELOG.md'
|
|
103
|
-
const commitSuccess = execCommand('git commit --message "📝
|
|
104
|
+
const addSuccess = execCommand('git add CHANGELOG.md');
|
|
105
|
+
const commitSuccess = execCommand('git commit --message "📝 Обновить changelog." --no-verify');
|
|
104
106
|
|
|
105
107
|
if (addSuccess && commitSuccess) {
|
|
106
|
-
logSuccess('📝', '
|
|
108
|
+
logSuccess('📝', 'Коммит с обновленным changelog создан.');
|
|
107
109
|
return true;
|
|
108
110
|
}
|
|
109
111
|
return false;
|
|
@@ -129,16 +131,16 @@ function askQuestion(question) {
|
|
|
129
131
|
async function extractTaskFromBranch() {
|
|
130
132
|
const currentBranch = getCurrentBranch();
|
|
131
133
|
if (!currentBranch) {
|
|
132
|
-
logError('❌', '
|
|
134
|
+
logError('❌', 'Не удалось получить имя текущей ветки.');
|
|
133
135
|
return null;
|
|
134
136
|
}
|
|
135
137
|
|
|
136
138
|
const taskMatch = currentBranch.match(/([a-zA-Z]+-[0-9]+)/);
|
|
137
139
|
if (!taskMatch) {
|
|
138
|
-
console.log(`⚠️ ${colors.yellow}
|
|
139
|
-
const task = await askQuestion('📝
|
|
140
|
+
console.log(`⚠️ ${colors.yellow}Имя ветки "${currentBranch}" не содержит номер задачи в формате [a-zA-Z]+-[0-9]+.${colors.reset}`);
|
|
141
|
+
const task = await askQuestion('📝 Введите номер задачи (например, SPEC-123): ');
|
|
140
142
|
if (!task || !task.match(/^[a-zA-Z]+-[0-9]+$/)) {
|
|
141
|
-
logError('❌', '
|
|
143
|
+
logError('❌', 'Неверный формат задачи. Ожидается формат: [a-zA-Z]+-[0-9]+');
|
|
142
144
|
return null;
|
|
143
145
|
}
|
|
144
146
|
return task;
|
|
@@ -152,24 +154,24 @@ async function getGitUser() {
|
|
|
152
154
|
let email = execSilent('git config user.email');
|
|
153
155
|
|
|
154
156
|
if (!name || !email) {
|
|
155
|
-
console.log(`⚠️ ${colors.yellow}
|
|
156
|
-
console.log(`💡
|
|
157
|
-
console.log(` git config user.name "
|
|
157
|
+
console.log(`⚠️ ${colors.yellow}Имя и email пользователя git не настроены.${colors.reset}`);
|
|
158
|
+
console.log(`💡 Их можно настроить так:`);
|
|
159
|
+
console.log(` git config user.name "Ваше Имя"`);
|
|
158
160
|
console.log(` git config user.email "your.email@domain.com"`);
|
|
159
161
|
console.log('');
|
|
160
162
|
|
|
161
163
|
if (!name) {
|
|
162
|
-
name = await askQuestion('👤
|
|
164
|
+
name = await askQuestion('👤 Введите ваше имя: ');
|
|
163
165
|
if (!name) {
|
|
164
|
-
logError('❌', '
|
|
166
|
+
logError('❌', 'Имя обязательно.');
|
|
165
167
|
return null;
|
|
166
168
|
}
|
|
167
169
|
}
|
|
168
170
|
|
|
169
171
|
if (!email) {
|
|
170
|
-
email = await askQuestion('📧
|
|
172
|
+
email = await askQuestion('📧 Введите ваш email: ');
|
|
171
173
|
if (!email || !email.includes('@')) {
|
|
172
|
-
logError('❌', '
|
|
174
|
+
logError('❌', 'Требуется корректный email.');
|
|
173
175
|
return null;
|
|
174
176
|
}
|
|
175
177
|
}
|
|
@@ -224,16 +226,16 @@ async function selectSection(availableSections) {
|
|
|
224
226
|
return availableSections[0];
|
|
225
227
|
}
|
|
226
228
|
|
|
227
|
-
console.log(`\n📋
|
|
229
|
+
console.log(`\n📋 Выберите раздел:`);
|
|
228
230
|
availableSections.forEach((section, index) => {
|
|
229
231
|
console.log(` ${index + 1}. ${section}`);
|
|
230
232
|
});
|
|
231
233
|
|
|
232
|
-
const choice = await askQuestion('\n🔢
|
|
234
|
+
const choice = await askQuestion('\n🔢 Введите номер раздела: ');
|
|
233
235
|
const choiceNum = parseInt(choice);
|
|
234
236
|
|
|
235
237
|
if (isNaN(choiceNum) || choiceNum < 1 || choiceNum > availableSections.length) {
|
|
236
|
-
logError('❌', '
|
|
238
|
+
logError('❌', 'Неверный выбор.');
|
|
237
239
|
return null;
|
|
238
240
|
}
|
|
239
241
|
|
|
@@ -241,7 +243,7 @@ async function selectSection(availableSections) {
|
|
|
241
243
|
}
|
|
242
244
|
|
|
243
245
|
function displayContext(lines, insertIndex, entry) {
|
|
244
|
-
console.log('\n📝 Changelog
|
|
246
|
+
console.log('\n📝 Changelog обновлен:');
|
|
245
247
|
|
|
246
248
|
if (insertIndex > 0) {
|
|
247
249
|
console.log(` ${colors.reset}${lines[insertIndex - 1]}${colors.reset}`);
|
|
@@ -256,26 +258,44 @@ function displayContext(lines, insertIndex, entry) {
|
|
|
256
258
|
}
|
|
257
259
|
|
|
258
260
|
async function changelogAppend(message) {
|
|
261
|
+
return runCommand({
|
|
262
|
+
name: 'changelog append',
|
|
263
|
+
checks: [
|
|
264
|
+
{ name: 'git-repo', run: requireGitRepo },
|
|
265
|
+
{ name: 'changelog-exists', run: () => requireFileExists('CHANGELOG.md') },
|
|
266
|
+
{ name: 'prettier-available', run: requirePrettierAvailable },
|
|
267
|
+
{ name: 'changelog-prettier-check', run: requireChangelogFormatted },
|
|
268
|
+
{
|
|
269
|
+
name: 'prepare-entry',
|
|
270
|
+
run: async () => prepareChangelogEntry(message)
|
|
271
|
+
}
|
|
272
|
+
],
|
|
273
|
+
steps: [
|
|
274
|
+
{
|
|
275
|
+
name: 'append-entry',
|
|
276
|
+
run: (ctx) => appendPreparedChangelogEntry(ctx)
|
|
277
|
+
}
|
|
278
|
+
]
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async function prepareChangelogEntry(message) {
|
|
259
283
|
try {
|
|
260
|
-
if (!checkChangelogWithPrettier()) {
|
|
261
|
-
return false;
|
|
262
|
-
}
|
|
263
284
|
const task = await extractTaskFromBranch();
|
|
264
|
-
if (!task) return false;
|
|
265
|
-
|
|
285
|
+
if (!task) return { ok: false, reason: 'Не удалось определить ID задачи.' };
|
|
286
|
+
|
|
266
287
|
const user = await getGitUser();
|
|
267
|
-
if (!user) return false;
|
|
268
|
-
|
|
288
|
+
if (!user) return { ok: false, reason: 'Не удалось определить пользователя git.' };
|
|
289
|
+
|
|
269
290
|
const formattedMessage = formatMessage(message);
|
|
270
291
|
const entry = `- ${task} ${formattedMessage} ${user}`;
|
|
271
|
-
|
|
272
292
|
const availableSections = detectSectionFromBranch();
|
|
273
293
|
const selectedSection = await selectSection(availableSections);
|
|
274
|
-
if (!selectedSection) return false;
|
|
275
|
-
|
|
294
|
+
if (!selectedSection) return { ok: false, reason: 'Не удалось выбрать раздел.' };
|
|
295
|
+
|
|
276
296
|
const changelog = fs.readFileSync('CHANGELOG.md', 'utf8');
|
|
277
297
|
const lines = changelog.split('\n');
|
|
278
|
-
|
|
298
|
+
|
|
279
299
|
let sectionIndex = -1;
|
|
280
300
|
for (let i = 0; i < lines.length; i++) {
|
|
281
301
|
if (lines[i] === selectedSection) {
|
|
@@ -283,27 +303,47 @@ async function changelogAppend(message) {
|
|
|
283
303
|
break;
|
|
284
304
|
}
|
|
285
305
|
}
|
|
286
|
-
|
|
306
|
+
|
|
287
307
|
if (sectionIndex === -1) {
|
|
288
|
-
|
|
308
|
+
return { ok: false, reason: `Не удалось найти раздел "${selectedSection}" в CHANGELOG.md.` };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
ok: true,
|
|
313
|
+
data: {
|
|
314
|
+
appendState: {
|
|
315
|
+
entry,
|
|
316
|
+
selectedSection,
|
|
317
|
+
lines,
|
|
318
|
+
sectionIndex
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
} catch (error) {
|
|
323
|
+
return { ok: false, reason: `Ошибка при подготовке записи changelog: ${error.message}` };
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function appendPreparedChangelogEntry(context) {
|
|
328
|
+
try {
|
|
329
|
+
const appendState = context.appendState;
|
|
330
|
+
if (!appendState) {
|
|
289
331
|
return false;
|
|
290
332
|
}
|
|
291
|
-
|
|
292
|
-
|
|
333
|
+
|
|
334
|
+
const lines = [...appendState.lines];
|
|
335
|
+
const selectedSection = appendState.selectedSection;
|
|
336
|
+
const entry = appendState.entry;
|
|
337
|
+
let insertIndex = appendState.sectionIndex + 2;
|
|
293
338
|
let defaultTextLines = 0;
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
while (insertIndex + defaultTextLines < lines.length &&
|
|
297
|
-
lines[insertIndex + defaultTextLines].startsWith('_')) {
|
|
339
|
+
|
|
340
|
+
while (insertIndex + defaultTextLines < lines.length && lines[insertIndex + defaultTextLines].startsWith('_')) {
|
|
298
341
|
defaultTextLines++;
|
|
299
342
|
}
|
|
300
|
-
|
|
301
|
-
// Insert the new entry
|
|
343
|
+
|
|
302
344
|
lines.splice(insertIndex, 0, entry);
|
|
303
|
-
|
|
304
|
-
// Remove default text lines if this is the first real entry in the section
|
|
345
|
+
|
|
305
346
|
if (defaultTextLines > 0) {
|
|
306
|
-
// Check if there are any non-default entries after our insertion
|
|
307
347
|
let hasOtherEntries = false;
|
|
308
348
|
for (let i = insertIndex + 1; i < insertIndex + 1 + defaultTextLines; i++) {
|
|
309
349
|
if (lines[i] && lines[i].startsWith('- ')) {
|
|
@@ -311,21 +351,18 @@ async function changelogAppend(message) {
|
|
|
311
351
|
break;
|
|
312
352
|
}
|
|
313
353
|
}
|
|
314
|
-
|
|
315
|
-
// If no other entries, remove default text
|
|
354
|
+
|
|
316
355
|
if (!hasOtherEntries) {
|
|
317
356
|
lines.splice(insertIndex + 1, defaultTextLines);
|
|
318
357
|
}
|
|
319
358
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
fs.writeFileSync('CHANGELOG.md', updatedChangelog);
|
|
323
|
-
|
|
359
|
+
|
|
360
|
+
fs.writeFileSync('CHANGELOG.md', lines.join('\n'));
|
|
324
361
|
displayContext(lines, insertIndex, entry);
|
|
325
|
-
logSuccess('✅',
|
|
362
|
+
logSuccess('✅', 'Запись добавлена в %s', selectedSection);
|
|
326
363
|
return true;
|
|
327
364
|
} catch (error) {
|
|
328
|
-
logError('❌',
|
|
365
|
+
logError('❌', 'Ошибка при добавлении записи changelog: %s', error.message);
|
|
329
366
|
return false;
|
|
330
367
|
}
|
|
331
368
|
}
|
|
@@ -341,5 +378,7 @@ module.exports = {
|
|
|
341
378
|
getGitUser,
|
|
342
379
|
formatMessage,
|
|
343
380
|
detectSectionFromBranch,
|
|
344
|
-
selectSection
|
|
381
|
+
selectSection,
|
|
382
|
+
prepareChangelogEntry,
|
|
383
|
+
appendPreparedChangelogEntry
|
|
345
384
|
};
|