spectrum-cli 1.0.7 → 1.1.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 +38 -2
- package/README.md +63 -13
- package/index.js +112 -93
- package/package.json +18 -2
- package/src/UNRELEASED.md +0 -1
- package/src/changelog.js +267 -2
- package/src/git.js +1 -1
- package/src/version.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -35,6 +35,42 @@ _Список правок для обеспечения безопасност
|
|
|
35
35
|
|
|
36
36
|
_Список правок для обеспечения технической поддержки._
|
|
37
37
|
|
|
38
|
+
## 🚀 [1.1.1] - 2025-10-03
|
|
39
|
+
|
|
40
|
+
### 🛠 Changed
|
|
41
|
+
|
|
42
|
+
- Исправлено поведения команды `version`, добавлены флаги `-v`/`--version`
|
|
43
|
+
- Добавлен этап проверки `CHANGELOG.md` через Prettier перед любыми изменениями
|
|
44
|
+
- Команды, изменяющие changelog, останавливаются при отсутствии `prettier` или ошибке `--check`
|
|
45
|
+
|
|
46
|
+
## 🚀 [1.1.0] - 2025-09-30
|
|
47
|
+
|
|
48
|
+
### 🆕 Added
|
|
49
|
+
|
|
50
|
+
- Добавлена команда `spectrum changelog append <message>` для умного добавления записей в CHANGELOG.md
|
|
51
|
+
- Автоматическое извлечение номера задачи из названия ветки (формат: [a-zA-Z]+-[0-9]+)
|
|
52
|
+
- Интерактивный запрос номера задачи при отсутствии в названии ветки
|
|
53
|
+
- Проверка Git config с предупреждениями и подсказками по настройке
|
|
54
|
+
- Умное определение раздела changelog по типу ветки (feature/bugfix/support)
|
|
55
|
+
- Интерактивный выбор раздела при неопределенности или множественных вариантах
|
|
56
|
+
- Автоматическое форматирование сообщений (добавление точки в конце)
|
|
57
|
+
- Контекстный вывод результата с цветовым выделением новой записи
|
|
58
|
+
- Автоматическое удаление дефолтного текста "_Список..._" при добавлении первой записи
|
|
59
|
+
- Установлен Jest для тестирования с полным покрытием utils функций
|
|
60
|
+
- Добавлены интеграционные тесты для проверки основной функциональности
|
|
61
|
+
|
|
62
|
+
### 🛠 Changed
|
|
63
|
+
|
|
64
|
+
- Обновлена документация README.md с подробным описанием команды `changelog append`
|
|
65
|
+
- Улучшена структура экспорта функций в модуле `src/changelog.js`
|
|
66
|
+
|
|
67
|
+
### 📦 Support
|
|
68
|
+
|
|
69
|
+
- Настроен Jest для модульного и интеграционного тестирования
|
|
70
|
+
- Добавлены npm scripts для запуска тестов (`test`, `test:watch`, `test:coverage`)
|
|
71
|
+
- Конфигурация Jest для Node.js окружения с покрытием кода
|
|
72
|
+
- Созданы базовые тесты для проверки стабильности функций
|
|
73
|
+
|
|
38
74
|
## 🚀 [1.0.1] - 2024-09-19
|
|
39
75
|
|
|
40
76
|
### 🆕 Added
|
|
@@ -64,9 +100,9 @@ _Список правок для обеспечения технической
|
|
|
64
100
|
- Создан CLI интерфейс `spectrum` с командой `commander`
|
|
65
101
|
- Добавлены основные команды:
|
|
66
102
|
- `spectrum release start` - полный цикл релиза
|
|
67
|
-
- `spectrum release close` - закрытие релиза
|
|
103
|
+
- `spectrum release close` - закрытие релиза
|
|
68
104
|
- `spectrum release deploy` - деплой релиза
|
|
69
|
-
- `spectrum up
|
|
105
|
+
- `spectrum version up major/minor/patch` - управление версиями
|
|
70
106
|
- Цветной вывод с эмодзи для лучшего UX
|
|
71
107
|
- Автоматическое определение пакетного менеджера (npm/yarn/bun)
|
|
72
108
|
- Обработка ошибок и fallback'ы
|
package/README.md
CHANGED
|
@@ -33,16 +33,21 @@ npm install
|
|
|
33
33
|
```bash
|
|
34
34
|
# Показать справку
|
|
35
35
|
spectrum --help
|
|
36
|
+
# Показать текущую версию CLI
|
|
37
|
+
spectrum -v | --version
|
|
36
38
|
|
|
37
39
|
# 📦 Управление версиями
|
|
38
|
-
spectrum up
|
|
39
|
-
spectrum up
|
|
40
|
-
spectrum up
|
|
40
|
+
spectrum version up major # Поднять major версию (1.0.0 → 2.0.0)
|
|
41
|
+
spectrum version up minor # Поднять minor версию (1.0.0 → 1.1.0)
|
|
42
|
+
spectrum version up patch # Поднять patch версию (1.0.0 → 1.0.1)
|
|
41
43
|
|
|
42
44
|
# 🚀 Управление релизами
|
|
43
45
|
spectrum release start # Полный цикл релиза
|
|
44
46
|
spectrum release close # Закрыть релиз и смержить в dev
|
|
45
47
|
spectrum release deploy # Деплой релиза (создать тег)
|
|
48
|
+
|
|
49
|
+
# 📝 Управление changelog
|
|
50
|
+
spectrum changelog append "Сообщение" # Добавить запись в CHANGELOG.md
|
|
46
51
|
```
|
|
47
52
|
|
|
48
53
|
### Справка по командам:
|
|
@@ -52,7 +57,10 @@ spectrum release deploy # Деплой релиза (создать тег)
|
|
|
52
57
|
spectrum release --help
|
|
53
58
|
|
|
54
59
|
# Справка по версиям
|
|
55
|
-
spectrum up
|
|
60
|
+
spectrum version up --help
|
|
61
|
+
|
|
62
|
+
# Справка по changelog
|
|
63
|
+
spectrum changelog --help
|
|
56
64
|
```
|
|
57
65
|
|
|
58
66
|
## ✨ Особенности
|
|
@@ -89,14 +97,15 @@ spectrum-cli/
|
|
|
89
97
|
|
|
90
98
|
### Команды:
|
|
91
99
|
|
|
92
|
-
| Команда
|
|
93
|
-
|
|
|
94
|
-
| `spectrum
|
|
95
|
-
| `spectrum
|
|
96
|
-
| `spectrum
|
|
97
|
-
| `spectrum
|
|
98
|
-
| `spectrum
|
|
99
|
-
| `spectrum
|
|
100
|
+
| Команда | Описание |
|
|
101
|
+
| ----------------------------- | ---------------------------- |
|
|
102
|
+
| `spectrum release start` | Запустить полный цикл релиза |
|
|
103
|
+
| `spectrum release deploy` | Деплой релиза |
|
|
104
|
+
| `spectrum release close` | Закрыть релиз |
|
|
105
|
+
| `spectrum version up major` | Увеличить major версию |
|
|
106
|
+
| `spectrum version up minor` | Увеличить minor версию |
|
|
107
|
+
| `spectrum version up patch` | Увеличить patch версию |
|
|
108
|
+
| `spectrum changelog append` | Добавить запись в changelog |
|
|
100
109
|
|
|
101
110
|
## 🔄 Workflow релиза
|
|
102
111
|
|
|
@@ -124,6 +133,48 @@ spectrum-cli/
|
|
|
124
133
|
2. Создает тег с текущей версией
|
|
125
134
|
3. Пушит тег в remote
|
|
126
135
|
|
|
136
|
+
## 📝 Работа с Changelog
|
|
137
|
+
|
|
138
|
+
### `spectrum changelog append "Сообщение"`
|
|
139
|
+
|
|
140
|
+
Умная команда для добавления записей в CHANGELOG.md с автоматическим определением контекста:
|
|
141
|
+
|
|
142
|
+
**✨ Основные возможности:**
|
|
143
|
+
- 🔍 **Автоматическое извлечение** номера задачи из названия ветки
|
|
144
|
+
- 🤔 **Интерактивный запрос** номера задачи если не найден
|
|
145
|
+
- 👤 **Проверка Git config** с подсказками по настройке
|
|
146
|
+
- 🎯 **Умное определение раздела** по типу ветки
|
|
147
|
+
- 🎨 **Контекстный вывод** с цветовым выделением
|
|
148
|
+
- 🧹 **Автоматическая очистка** дефолтного текста
|
|
149
|
+
- ✅ **Проверка Prettier** перед изменениями `CHANGELOG.md` (команда остановится при ошибке)
|
|
150
|
+
|
|
151
|
+
**📋 Поддерживаемые типы веток:**
|
|
152
|
+
- `feature/` → выбор между Added, Changed, Deprecated, Removed
|
|
153
|
+
- `bugfix/`, `fix/` → автоматически Fixed
|
|
154
|
+
- `support/` → выбор между Support, Security
|
|
155
|
+
- другие → показ всех разделов
|
|
156
|
+
|
|
157
|
+
**📌 Пример использования:**
|
|
158
|
+
```bash
|
|
159
|
+
# На ветке feature/ABC-123-new-component
|
|
160
|
+
spectrum changelog append "Добавлен новый компонент кнопки"
|
|
161
|
+
|
|
162
|
+
# Результат в CHANGELOG.md:
|
|
163
|
+
# - ABC-123 Добавлен новый компонент кнопки. [Ваше Имя](ваш@email.com)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### 🔍 Линтинг CHANGELOG с Prettier
|
|
167
|
+
|
|
168
|
+
- Перед любыми изменениями changelog выполняется проверка наличия `prettier` и команда `prettier --check CHANGELOG.md`.
|
|
169
|
+
- Если `prettier` недоступен или проверка не пройдена, выполнение прекращается с ошибкой.
|
|
170
|
+
- Запуск через `npx` поддерживается автоматически: используется `npx --yes prettier` при наличии.
|
|
171
|
+
|
|
172
|
+
> Совет: Приведите файл к нужному формату командой:
|
|
173
|
+
>
|
|
174
|
+
> ```bash
|
|
175
|
+
> npx --yes prettier --write CHANGELOG.md
|
|
176
|
+
> ```
|
|
177
|
+
|
|
127
178
|
## 🎯 Философия
|
|
128
179
|
|
|
129
180
|
- **Простота** - минимум команд, максимум функциональности
|
|
@@ -135,7 +186,6 @@ spectrum-cli/
|
|
|
135
186
|
|
|
136
187
|
- 📋 **[Установка](./INSTALL.md)** - подробные инструкции по установке
|
|
137
188
|
- 🚀 **[Релизы](./RELEASE.md)** - инструкции по выпуску новых версий
|
|
138
|
-
- 📦 **[Дистрибьюция](./DISTRIBUTION.md)** - варианты распространения
|
|
139
189
|
|
|
140
190
|
## 🤝 Участие в разработке
|
|
141
191
|
|
package/index.js
CHANGED
|
@@ -1,114 +1,133 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const { Command } = require(
|
|
2
|
+
const { Command } = require("commander");
|
|
3
3
|
const program = new Command();
|
|
4
4
|
|
|
5
|
-
const git = require(
|
|
6
|
-
const version = require(
|
|
7
|
-
const release = require(
|
|
5
|
+
const git = require("./src/git");
|
|
6
|
+
const version = require("./src/version");
|
|
7
|
+
const release = require("./src/release");
|
|
8
|
+
const changelog = require("./src/changelog");
|
|
9
|
+
const { version: pkgVersion } = require("./package.json");
|
|
8
10
|
|
|
9
11
|
program
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
.name("spectrum")
|
|
13
|
+
.description("🚀 Spectrum CLI for development workflow")
|
|
14
|
+
.version(pkgVersion, "-v, --version");
|
|
13
15
|
|
|
14
16
|
// Release commands
|
|
15
17
|
const releaseCmd = program
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
.command("release")
|
|
19
|
+
.description("Release management commands");
|
|
18
20
|
|
|
19
21
|
releaseCmd
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
.command("start")
|
|
23
|
+
.description("Start full release process")
|
|
24
|
+
.action(() => release.releaseStart());
|
|
23
25
|
|
|
24
26
|
releaseCmd
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
.command("close")
|
|
28
|
+
.description("Close release and merge to dev")
|
|
29
|
+
.action(() => release.releaseClose());
|
|
28
30
|
|
|
29
31
|
releaseCmd
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
//
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
32
|
+
.command("deploy")
|
|
33
|
+
.description("Deploy release (create and push tag)")
|
|
34
|
+
.action(() => git.gitCreateTagAndPush());
|
|
35
|
+
|
|
36
|
+
// Version up commands
|
|
37
|
+
const versionCmd = program
|
|
38
|
+
.command("version")
|
|
39
|
+
.description("Update version using semantic versioning");
|
|
40
|
+
|
|
41
|
+
const upCmd = versionCmd
|
|
42
|
+
.command("up")
|
|
43
|
+
.description("Update version using semantic versioning");
|
|
44
|
+
|
|
45
|
+
upCmd
|
|
46
|
+
.command("major")
|
|
47
|
+
.description("Bump major version (x.0.0)")
|
|
48
|
+
.action(() => version.setVersionTypescript("major"));
|
|
49
|
+
|
|
50
|
+
upCmd
|
|
51
|
+
.command("minor")
|
|
52
|
+
.description("Bump minor version (x.y.0)")
|
|
53
|
+
.action(() => version.setVersionTypescript("minor"));
|
|
54
|
+
|
|
55
|
+
upCmd
|
|
56
|
+
.command("patch")
|
|
57
|
+
.description("Bump patch version (x.y.z)")
|
|
58
|
+
.action(() => version.setVersionTypescript("patch"));
|
|
59
|
+
|
|
60
|
+
// Changelog commands
|
|
61
|
+
const changelogCmd = program
|
|
62
|
+
.command("changelog")
|
|
63
|
+
.description("Changelog management commands");
|
|
64
|
+
|
|
65
|
+
changelogCmd
|
|
66
|
+
.command("append <message>")
|
|
67
|
+
.description("Append entry to changelog with task number from branch")
|
|
68
|
+
.action(async (message) => {
|
|
69
|
+
try {
|
|
70
|
+
await changelog.changelogAppend(message);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error(`❌ Error: ${error.message}`);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
57
76
|
|
|
58
77
|
// Override help to show custom format with aliases
|
|
59
78
|
program.configureHelp({
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
cmdLine = cmdLine.padEnd(40) + description;
|
|
95
|
-
output += cmdLine + '\n';
|
|
96
|
-
});
|
|
97
|
-
output += '\n';
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Options
|
|
101
|
-
if (options.length > 0) {
|
|
102
|
-
output += 'Options:\n';
|
|
103
|
-
options.forEach(option => {
|
|
104
|
-
const flags = helper.optionTerm(option);
|
|
105
|
-
const description = helper.optionDescription(option);
|
|
106
|
-
output += ` ${flags.padEnd(25)} ${description}\n`;
|
|
107
|
-
});
|
|
79
|
+
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
|
+
const cmdUsage = helper.commandUsage(cmd);
|
|
85
|
+
const cmdDescription = helper.commandDescription(cmd);
|
|
86
|
+
const options = helper.visibleOptions(cmd);
|
|
87
|
+
const commands = helper.visibleCommands(cmd);
|
|
88
|
+
|
|
89
|
+
let output = "";
|
|
90
|
+
|
|
91
|
+
// Usage
|
|
92
|
+
if (cmdUsage) {
|
|
93
|
+
output += `Usage: ${cmdUsage}\n\n`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Description
|
|
97
|
+
if (cmdDescription) {
|
|
98
|
+
output += `${cmdDescription}\n\n`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Commands with aliases
|
|
102
|
+
if (commands.length > 0) {
|
|
103
|
+
output += "Commands:\n";
|
|
104
|
+
commands.forEach((cmd) => {
|
|
105
|
+
const nameAndArgs = cmd.name() + cmd.usage().replace(/^[^\s]+\s*/, " ");
|
|
106
|
+
const aliases = cmd.aliases();
|
|
107
|
+
const description = cmd.description();
|
|
108
|
+
|
|
109
|
+
let cmdLine = ` ${nameAndArgs}`;
|
|
110
|
+
if (aliases.length > 0) {
|
|
111
|
+
cmdLine = cmdLine.padEnd(25) + `[${aliases.join(", ")}]`;
|
|
108
112
|
}
|
|
109
|
-
|
|
110
|
-
|
|
113
|
+
cmdLine = cmdLine.padEnd(40) + description;
|
|
114
|
+
output += cmdLine + "\n";
|
|
115
|
+
});
|
|
116
|
+
output += "\n";
|
|
111
117
|
}
|
|
118
|
+
|
|
119
|
+
// Options
|
|
120
|
+
if (options.length > 0) {
|
|
121
|
+
output += "Options:\n";
|
|
122
|
+
options.forEach((option) => {
|
|
123
|
+
const flags = helper.optionTerm(option);
|
|
124
|
+
const description = helper.optionDescription(option);
|
|
125
|
+
output += ` ${flags.padEnd(25)} ${description}\n`;
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return output;
|
|
130
|
+
},
|
|
112
131
|
});
|
|
113
132
|
|
|
114
|
-
program.parse();
|
|
133
|
+
program.parse();
|
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spectrum-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "🚀 Modern CLI for development workflow automation - release management, versioning, and changelog generation",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"spectrum": "./index.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"test": "
|
|
10
|
+
"test": "jest",
|
|
11
|
+
"test:watch": "jest --watch",
|
|
12
|
+
"test:coverage": "jest --coverage",
|
|
11
13
|
"prepublishOnly": "echo \"Publishing spectrum-cli v$(node -p 'require(\"./package.json\").version')\"",
|
|
12
14
|
"postpack": "echo \"Packed successfully\""
|
|
13
15
|
},
|
|
@@ -61,5 +63,19 @@
|
|
|
61
63
|
"preferGlobal": true,
|
|
62
64
|
"publishConfig": {
|
|
63
65
|
"access": "public"
|
|
66
|
+
},
|
|
67
|
+
"devDependencies": {
|
|
68
|
+
"jest": "^30.2.0"
|
|
69
|
+
},
|
|
70
|
+
"jest": {
|
|
71
|
+
"testEnvironment": "node",
|
|
72
|
+
"collectCoverageFrom": [
|
|
73
|
+
"src/**/*.js",
|
|
74
|
+
"index.js"
|
|
75
|
+
],
|
|
76
|
+
"testMatch": [
|
|
77
|
+
"**/__tests__/**/*.js",
|
|
78
|
+
"**/*.test.js"
|
|
79
|
+
]
|
|
64
80
|
}
|
|
65
81
|
}
|
package/src/UNRELEASED.md
CHANGED
package/src/changelog.js
CHANGED
|
@@ -1,10 +1,42 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
|
-
const
|
|
4
|
+
const readline = require('readline');
|
|
5
|
+
const { logSuccess, logError, execCommand, execSilent, getVersion, getCurrentBranch, colors } = require('./utils');
|
|
6
|
+
|
|
7
|
+
function getPrettierRunner() {
|
|
8
|
+
const npxVersion = execSilent('npx --yes prettier --version');
|
|
9
|
+
if (npxVersion) return 'npx --yes prettier';
|
|
10
|
+
const directVersion = execSilent('prettier --version');
|
|
11
|
+
if (directVersion) return 'prettier';
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function ensurePrettierAvailable() {
|
|
16
|
+
const runner = getPrettierRunner();
|
|
17
|
+
if (!runner) {
|
|
18
|
+
logError('❌', 'Prettier is not available. Install it or use npx.');
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
return runner;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function checkChangelogWithPrettier() {
|
|
25
|
+
const runner = ensurePrettierAvailable();
|
|
26
|
+
if (!runner) return false;
|
|
27
|
+
const ok = execCommand(`${runner} --check CHANGELOG.md`, null, null);
|
|
28
|
+
if (!ok) {
|
|
29
|
+
logError('❌', 'CHANGELOG.md failed Prettier check. Fix formatting before proceeding.');
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
5
34
|
|
|
6
35
|
function changelogChangeHeader() {
|
|
7
36
|
try {
|
|
37
|
+
if (!checkChangelogWithPrettier()) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
8
40
|
const currentVersion = getVersion();
|
|
9
41
|
if (!currentVersion) {
|
|
10
42
|
return false;
|
|
@@ -26,6 +58,9 @@ function changelogChangeHeader() {
|
|
|
26
58
|
|
|
27
59
|
function changelogRemoveEmptyChapters() {
|
|
28
60
|
try {
|
|
61
|
+
if (!checkChangelogWithPrettier()) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
29
64
|
const data = fs.readFileSync('CHANGELOG.md', 'utf8');
|
|
30
65
|
const updatedData = data.replaceAll(/###.*\n\n_.*_\n\n/gm, '');
|
|
31
66
|
fs.writeFileSync('CHANGELOG.md', updatedData);
|
|
@@ -39,6 +74,9 @@ function changelogRemoveEmptyChapters() {
|
|
|
39
74
|
|
|
40
75
|
function changelogAddUnreleasedBlock() {
|
|
41
76
|
try {
|
|
77
|
+
if (!checkChangelogWithPrettier()) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
42
80
|
const currentVersion = getVersion();
|
|
43
81
|
if (!currentVersion) {
|
|
44
82
|
return false;
|
|
@@ -71,10 +109,237 @@ function changelogCommit() {
|
|
|
71
109
|
return false;
|
|
72
110
|
}
|
|
73
111
|
|
|
112
|
+
function createReadlineInterface() {
|
|
113
|
+
return readline.createInterface({
|
|
114
|
+
input: process.stdin,
|
|
115
|
+
output: process.stdout
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function askQuestion(question) {
|
|
120
|
+
return new Promise((resolve) => {
|
|
121
|
+
const rl = createReadlineInterface();
|
|
122
|
+
rl.question(question, (answer) => {
|
|
123
|
+
rl.close();
|
|
124
|
+
resolve(answer.trim());
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function extractTaskFromBranch() {
|
|
130
|
+
const currentBranch = getCurrentBranch();
|
|
131
|
+
if (!currentBranch) {
|
|
132
|
+
logError('❌', 'Cannot get current branch name.');
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const taskMatch = currentBranch.match(/([a-zA-Z]+-[0-9]+)/);
|
|
137
|
+
if (!taskMatch) {
|
|
138
|
+
console.log(`⚠️ ${colors.yellow}Branch name "${currentBranch}" does not contain task number in format [a-zA-Z]+-[0-9]+.${colors.reset}`);
|
|
139
|
+
const task = await askQuestion('📝 Please enter task number (e.g. SPEC-123): ');
|
|
140
|
+
if (!task || !task.match(/^[a-zA-Z]+-[0-9]+$/)) {
|
|
141
|
+
logError('❌', 'Invalid task format. Expected format: [a-zA-Z]+-[0-9]+');
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
return task;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return taskMatch[1];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function getGitUser() {
|
|
151
|
+
let name = execSilent('git config user.name');
|
|
152
|
+
let email = execSilent('git config user.email');
|
|
153
|
+
|
|
154
|
+
if (!name || !email) {
|
|
155
|
+
console.log(`⚠️ ${colors.yellow}Git user name and email are not configured.${colors.reset}`);
|
|
156
|
+
console.log(`💡 You can configure them using:`);
|
|
157
|
+
console.log(` git config user.name "Your Name"`);
|
|
158
|
+
console.log(` git config user.email "your.email@domain.com"`);
|
|
159
|
+
console.log('');
|
|
160
|
+
|
|
161
|
+
if (!name) {
|
|
162
|
+
name = await askQuestion('👤 Please enter your name: ');
|
|
163
|
+
if (!name) {
|
|
164
|
+
logError('❌', 'Name is required.');
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (!email) {
|
|
170
|
+
email = await askQuestion('📧 Please enter your email: ');
|
|
171
|
+
if (!email || !email.includes('@')) {
|
|
172
|
+
logError('❌', 'Valid email is required.');
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return `[${name}](${email})`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function formatMessage(message) {
|
|
182
|
+
if (!message.endsWith('.')) {
|
|
183
|
+
return message + '.';
|
|
184
|
+
}
|
|
185
|
+
return message;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function detectSectionFromBranch() {
|
|
189
|
+
const currentBranch = getCurrentBranch();
|
|
190
|
+
if (!currentBranch) return [];
|
|
191
|
+
|
|
192
|
+
const branchLower = currentBranch.toLowerCase();
|
|
193
|
+
const sections = {
|
|
194
|
+
support: ['### 📦 Support', '### 🔐 Security'],
|
|
195
|
+
bugfix: ['### 🪲 Fixed'],
|
|
196
|
+
feature: ['### 🆕 Added', '### 🛠 Changed', '### 📜 Deprecated', '### 🗑 Removed']
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
if (branchLower.includes('support')) {
|
|
200
|
+
return sections.support;
|
|
201
|
+
} else if (branchLower.includes('bugfix') || branchLower.includes('fix')) {
|
|
202
|
+
return sections.bugfix;
|
|
203
|
+
} else if (branchLower.includes('feature') || branchLower.includes('feat')) {
|
|
204
|
+
return sections.feature;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return [];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function selectSection(availableSections) {
|
|
211
|
+
if (availableSections.length === 0) {
|
|
212
|
+
availableSections = [
|
|
213
|
+
'### 🆕 Added',
|
|
214
|
+
'### 🛠 Changed',
|
|
215
|
+
'### 📜 Deprecated',
|
|
216
|
+
'### 🗑 Removed',
|
|
217
|
+
'### 🪲 Fixed',
|
|
218
|
+
'### 🔐 Security',
|
|
219
|
+
'### 📦 Support'
|
|
220
|
+
];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (availableSections.length === 1) {
|
|
224
|
+
return availableSections[0];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
console.log(`\n📋 Please select a section:`);
|
|
228
|
+
availableSections.forEach((section, index) => {
|
|
229
|
+
console.log(` ${index + 1}. ${section}`);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const choice = await askQuestion('\n🔢 Enter section number: ');
|
|
233
|
+
const choiceNum = parseInt(choice);
|
|
234
|
+
|
|
235
|
+
if (isNaN(choiceNum) || choiceNum < 1 || choiceNum > availableSections.length) {
|
|
236
|
+
logError('❌', 'Invalid choice.');
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return availableSections[choiceNum - 1];
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function displayContext(lines, insertIndex, entry) {
|
|
244
|
+
console.log('\n📝 Changelog updated:');
|
|
245
|
+
|
|
246
|
+
if (insertIndex > 0) {
|
|
247
|
+
console.log(` ${colors.reset}${lines[insertIndex - 1]}${colors.reset}`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
console.log(` ${colors.green}${entry}${colors.reset}`);
|
|
251
|
+
|
|
252
|
+
if (insertIndex < lines.length - 1) {
|
|
253
|
+
console.log(` ${colors.reset}${lines[insertIndex]}${colors.reset}`);
|
|
254
|
+
}
|
|
255
|
+
console.log('');
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async function changelogAppend(message) {
|
|
259
|
+
try {
|
|
260
|
+
if (!checkChangelogWithPrettier()) {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
const task = await extractTaskFromBranch();
|
|
264
|
+
if (!task) return false;
|
|
265
|
+
|
|
266
|
+
const user = await getGitUser();
|
|
267
|
+
if (!user) return false;
|
|
268
|
+
|
|
269
|
+
const formattedMessage = formatMessage(message);
|
|
270
|
+
const entry = `- ${task} ${formattedMessage} ${user}`;
|
|
271
|
+
|
|
272
|
+
const availableSections = detectSectionFromBranch();
|
|
273
|
+
const selectedSection = await selectSection(availableSections);
|
|
274
|
+
if (!selectedSection) return false;
|
|
275
|
+
|
|
276
|
+
const changelog = fs.readFileSync('CHANGELOG.md', 'utf8');
|
|
277
|
+
const lines = changelog.split('\n');
|
|
278
|
+
|
|
279
|
+
let sectionIndex = -1;
|
|
280
|
+
for (let i = 0; i < lines.length; i++) {
|
|
281
|
+
if (lines[i] === selectedSection) {
|
|
282
|
+
sectionIndex = i;
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (sectionIndex === -1) {
|
|
288
|
+
logError('❌', `Cannot find "${selectedSection}" section in CHANGELOG.md.`);
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
let insertIndex = sectionIndex + 2;
|
|
293
|
+
let defaultTextLines = 0;
|
|
294
|
+
|
|
295
|
+
// Count and remember default text lines to remove them
|
|
296
|
+
while (insertIndex + defaultTextLines < lines.length &&
|
|
297
|
+
lines[insertIndex + defaultTextLines].startsWith('_')) {
|
|
298
|
+
defaultTextLines++;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Insert the new entry
|
|
302
|
+
lines.splice(insertIndex, 0, entry);
|
|
303
|
+
|
|
304
|
+
// Remove default text lines if this is the first real entry in the section
|
|
305
|
+
if (defaultTextLines > 0) {
|
|
306
|
+
// Check if there are any non-default entries after our insertion
|
|
307
|
+
let hasOtherEntries = false;
|
|
308
|
+
for (let i = insertIndex + 1; i < insertIndex + 1 + defaultTextLines; i++) {
|
|
309
|
+
if (lines[i] && lines[i].startsWith('- ')) {
|
|
310
|
+
hasOtherEntries = true;
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// If no other entries, remove default text
|
|
316
|
+
if (!hasOtherEntries) {
|
|
317
|
+
lines.splice(insertIndex + 1, defaultTextLines);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const updatedChangelog = lines.join('\n');
|
|
322
|
+
fs.writeFileSync('CHANGELOG.md', updatedChangelog);
|
|
323
|
+
|
|
324
|
+
displayContext(lines, insertIndex, entry);
|
|
325
|
+
logSuccess('✅', `Entry added to ${selectedSection}`);
|
|
326
|
+
return true;
|
|
327
|
+
} catch (error) {
|
|
328
|
+
logError('❌', `Error adding changelog entry: ${error.message}`);
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
74
333
|
|
|
75
334
|
module.exports = {
|
|
76
335
|
changelogChangeHeader,
|
|
77
336
|
changelogRemoveEmptyChapters,
|
|
78
337
|
changelogAddUnreleasedBlock,
|
|
79
|
-
changelogCommit
|
|
338
|
+
changelogCommit,
|
|
339
|
+
changelogAppend,
|
|
340
|
+
extractTaskFromBranch,
|
|
341
|
+
getGitUser,
|
|
342
|
+
formatMessage,
|
|
343
|
+
detectSectionFromBranch,
|
|
344
|
+
selectSection
|
|
80
345
|
};
|
package/src/git.js
CHANGED
|
@@ -87,7 +87,7 @@ function gitCreateTagAndPush() {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
if (execCommand(`git tag v${currentVersion}`, null, null)) {
|
|
90
|
-
logSuccess('
|
|
90
|
+
logSuccess('🔖', `Create tag v${currentVersion}.`);
|
|
91
91
|
|
|
92
92
|
if (execCommand(`git push origin v${currentVersion}`, null, null)) {
|
|
93
93
|
logSuccess('🚀', `Push tag v${currentVersion}.`);
|
package/src/version.js
CHANGED
|
@@ -34,7 +34,7 @@ function setVersionTypescript(versionType) {
|
|
|
34
34
|
packageJson.version = newVersion;
|
|
35
35
|
fs.writeFileSync('package.json', JSON.stringify(packageJson, null, 2) + '\n');
|
|
36
36
|
|
|
37
|
-
logSuccess('
|
|
37
|
+
logSuccess('🔖', `Current version ${oldVersion} up to ${colors.green}${newVersion}${colors.reset}.`);
|
|
38
38
|
return true;
|
|
39
39
|
} catch (error) {
|
|
40
40
|
logError('❌', 'Error updating version in package.json');
|