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 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 version major/minor/patch` - управление версиями
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 version major # Поднять major версию (1.0.0 → 2.0.0)
39
- spectrum up version minor # Поднять minor версию (1.0.0 → 1.1.0)
40
- spectrum up version patch # Поднять patch версию (1.0.0 → 1.0.1)
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 version --help
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 up version major` | Увеличить major версию |
95
- | `spectrum up version minor` | Увеличить minor версию |
96
- | `spectrum up version patch` | Увеличить patch версию |
97
- | `spectrum release start` | Запустить полный цикл релиза |
98
- | `spectrum release close` | Закрыть релиз |
99
- | `spectrum release deploy` | Деплой релиза |
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('commander');
2
+ const { Command } = require("commander");
3
3
  const program = new Command();
4
4
 
5
- const git = require('./src/git');
6
- const version = require('./src/version');
7
- const release = require('./src/release');
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
- .name('spectrum')
11
- .description('🚀 Spectrum CLI for development workflow')
12
- .version('1.0.7');
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
- .command('release')
17
- .description('Release management commands');
18
+ .command("release")
19
+ .description("Release management commands");
18
20
 
19
21
  releaseCmd
20
- .command('start')
21
- .description('Start full release process')
22
- .action(() => release.releaseStart());
22
+ .command("start")
23
+ .description("Start full release process")
24
+ .action(() => release.releaseStart());
23
25
 
24
26
  releaseCmd
25
- .command('close')
26
- .description('Close release and merge to dev')
27
- .action(() => release.releaseClose());
27
+ .command("close")
28
+ .description("Close release and merge to dev")
29
+ .action(() => release.releaseClose());
28
30
 
29
31
  releaseCmd
30
- .command('deploy')
31
- .description('Deploy release (create and push tag)')
32
- .action(() => git.gitCreateTagAndPush());
33
-
34
- // Up version commands
35
- const upCmd = program
36
- .command('up')
37
- .description('Version management commands');
38
-
39
- const versionCmd = upCmd
40
- .command('version')
41
- .description('Update version using semantic versioning');
42
-
43
- versionCmd
44
- .command('major')
45
- .description('Bump major version (x.0.0)')
46
- .action(() => version.setVersionTypescript('major'));
47
-
48
- versionCmd
49
- .command('minor')
50
- .description('Bump minor version (x.y.0)')
51
- .action(() => version.setVersionTypescript('minor'));
52
-
53
- versionCmd
54
- .command('patch')
55
- .description('Bump patch version (x.y.z)')
56
- .action(() => version.setVersionTypescript('patch'));
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
- formatHelp: (cmd, helper) => {
61
- const termWidth = helper.padWidth(cmd, helper);
62
- const helpWidth = helper.helpWidth || 80;
63
- const itemIndentWidth = 2;
64
- const itemSeparatorWidth = 2;
65
- const cmdUsage = helper.commandUsage(cmd);
66
- const cmdDescription = helper.commandDescription(cmd);
67
- const options = helper.visibleOptions(cmd);
68
- const commands = helper.visibleCommands(cmd);
69
-
70
- let output = '';
71
-
72
- // Usage
73
- if (cmdUsage) {
74
- output += `Usage: ${cmdUsage}\n\n`;
75
- }
76
-
77
- // Description
78
- if (cmdDescription) {
79
- output += `${cmdDescription}\n\n`;
80
- }
81
-
82
- // Commands with aliases
83
- if (commands.length > 0) {
84
- output += 'Commands:\n';
85
- commands.forEach(cmd => {
86
- const nameAndArgs = cmd.name() + cmd.usage().replace(/^[^\s]+\s*/, ' ');
87
- const aliases = cmd.aliases();
88
- const description = cmd.description();
89
-
90
- let cmdLine = ` ${nameAndArgs}`;
91
- if (aliases.length > 0) {
92
- cmdLine = cmdLine.padEnd(25) + `[${aliases.join(', ')}]`;
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
- return output;
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.0.7",
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": "echo \"Error: no test specified\" && exit 1",
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
@@ -27,4 +27,3 @@ _Список правок для обеспечения безопасност
27
27
  ### 📦 Support
28
28
 
29
29
  _Список правок для обеспечения технической поддержки._
30
-
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 { logSuccess, execCommand, getVersion, colors } = require('./utils');
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('🏷️', `Create tag v${currentVersion}.`);
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('🏷️', `Current version ${oldVersion} up to ${colors.green}${newVersion}${colors.reset}.`);
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');