@vv0rkz/js-template 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,150 @@
1
+ # @vv0rkz/js-template
2
+
3
+ ⚡ Переиспользуемый шаблон для JS проектов с husky, changelog, GitHub tools
4
+
5
+ ## 🚀 Установка
6
+
7
+ npm install -D @vv0rkz/js-template
8
+ npx jst init
9
+
10
+ ## 💻 Использование
11
+
12
+ ```bash
13
+ # Короткая команда (рекомендуется)
14
+
15
+ npm run _ tasks
16
+ npm run _ release
17
+ npm run _ create-task "Новая фича"
18
+
19
+ # Или полная
20
+ npm run jst tasks
21
+ npm run jst release
22
+ ```
23
+
24
+ ## 📋 Команды
25
+
26
+ ### Управление проектом
27
+
28
+ - `jst init` — инициализация проекта
29
+
30
+ ### Разработка
31
+
32
+ - `jst changelog` — создать changelog
33
+ - `jst release` — полный релиз (проверка + changelog + README)
34
+ - `jst update-readme` — обновить README
35
+ - `jst push-release` — запушить релиз в main
36
+
37
+ ### Управление задачами
38
+
39
+ - `jst tasks` — список открытых задач
40
+ - `jst create-task [название]` — создать задачу
41
+ - `jst bugs` — список открытых багов
42
+ - `jst create-bug [название]` — создать баг
43
+ - `jst all-issues` — все открытые issues
44
+
45
+ ## 📦 Что устанавливается
46
+
47
+ - ✅ Husky + хуки (commit-msg, pre-push, post-commit)
48
+ - ✅ Commitlint конфиг (проверка коммитов)
49
+ - ✅ Changelogen конфиг (автогенерация changelog)
50
+ - ✅ GitHub tools скрипты (управление задачами)
51
+ - ✅ .gitignore (готовый файл)
52
+
53
+ ## 🎯 Пример использования
54
+
55
+ ### Установка в новый проект
56
+
57
+ ```bash
58
+ mkdir my-awesome-js-project
59
+ cd my-awesome-js-project
60
+ npm init -y
61
+ npm install -D @vv0rkz/js-template
62
+ npx jst init
63
+ ```
64
+
65
+ ### Работа с задачами
66
+
67
+ ```bash
68
+ Создать задачу
69
+ npm run _ create-task "Добавить темную тему"
70
+
71
+ Посмотреть все задачи
72
+ npm run _ tasks
73
+
74
+ Создать баг
75
+ npm run _ create-bug "Кнопка не работает"
76
+
77
+ Посмотреть баги
78
+ npm run _ bugs
79
+ ```
80
+
81
+ ### Релиз версии
82
+
83
+ ```
84
+ Сделать фичу
85
+ git add .
86
+ git commit -m "feat: добавлена темная тема"
87
+
88
+ Создать релиз
89
+ npm run _ release
90
+
91
+ Запушить в main
92
+ npm run _ push-release
93
+ ```
94
+
95
+ ## 📁 Структура проекта после установки
96
+
97
+ ```
98
+ my-project/
99
+ ├── .husky/
100
+ │ ├── commit-msg # Проверка формата коммитов
101
+ │ ├── pre-push # Запуск тестов перед push
102
+ │ └── post-commit # Сообщение после коммита
103
+ ├── tools-gh/ # GitHub утилиты
104
+ │ ├── create-task.js
105
+ │ ├── create-bug.js
106
+ │ ├── update-readme.js
107
+ │ └── ...
108
+ ├── .gitignore # Готовый .gitignore
109
+ ├── changelog.config.js # Конфиг для changelog
110
+ ├── commitlint.config.js # Правила для коммитов
111
+ └── package.json # С готовыми скриптами
112
+ ```
113
+
114
+ ## 🛠️ Технологии
115
+
116
+ - [Husky](https://typicode.github.io/husky/) — Git hooks
117
+ - [Commitlint](https://commitlint.js.org/) — Проверка коммитов
118
+ - [Changelogen](https://github.com/unjs/changelogen) — Генерация changelog
119
+ - [GitHub CLI](https://cli.github.com/) — Управление issues
120
+
121
+ ## 📝 Формат коммитов
122
+
123
+ feat: новая функция
124
+ fix: исправление бага
125
+ docs: изменения в документации
126
+ refactor: рефакторинг кода
127
+ perf: улучшение производительности
128
+ test: добавление тестов
129
+ chore: обновление зависимостей
130
+
131
+ ## 📄 Лицензия
132
+
133
+ MIT © [vv0rkz](https://github.com/vv0rkz)
134
+
135
+ ```bash
136
+ # 🚀 Публикация
137
+ cd js-template
138
+ npm login
139
+ npm publish --access public
140
+ # 💻 Использование в проекте
141
+
142
+ # В новом проекте
143
+ npm install -D @vv0rkz/js-template
144
+ npx jst init
145
+
146
+ # Работа через короткую команду _
147
+ npm run _ tasks
148
+ npm run _ release
149
+ npm run _ create-task "Моя задача"
150
+ ```
package/bin/cli.js ADDED
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env node
2
+ import { fileURLToPath } from 'url'
3
+ import { dirname, join } from 'path'
4
+ import { spawnSync } from 'child_process'
5
+ import { existsSync } from 'fs'
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url))
8
+ const toolsDir = join(__dirname, '../tools-gh')
9
+
10
+ const args = process.argv.slice(2)
11
+ const command = args[0]
12
+ const commandArgs = args.slice(1)
13
+
14
+ // Проверка наличия tools-gh в текущем проекте
15
+ const projectToolsDir = join(process.cwd(), 'tools-gh')
16
+ const useProjectTools = existsSync(projectToolsDir)
17
+ const scriptsDir = useProjectTools ? projectToolsDir : toolsDir
18
+
19
+ const commands = {
20
+ init: () => {
21
+ const initScript = join(__dirname, 'init.js')
22
+ spawnSync('node', [initScript], { stdio: 'inherit' })
23
+ },
24
+
25
+ changelog: () => {
26
+ spawnSync('npx', ['changelogen', ...commandArgs], { stdio: 'inherit', shell: true })
27
+ },
28
+
29
+ release: () => {
30
+ console.log('🚀 Запуск релиза...\n')
31
+
32
+ const checkDemo = spawnSync('node', [join(scriptsDir, 'check-demo-for-release.js')], { stdio: 'inherit' })
33
+ if (checkDemo.status !== 0) {
34
+ console.error('❌ Проверка демо не прошла')
35
+ process.exit(1)
36
+ }
37
+
38
+ const changelog = spawnSync('npx', ['changelogen', '--release'], { stdio: 'inherit', shell: true })
39
+ if (changelog.status !== 0) {
40
+ console.error('❌ Ошибка создания changelog')
41
+ process.exit(1)
42
+ }
43
+
44
+ spawnSync('node', [join(scriptsDir, 'update-readme.js')], { stdio: 'inherit' })
45
+ console.log('\n✅ Релиз успешно создан!')
46
+ },
47
+
48
+ 'update-readme': () => {
49
+ spawnSync('node', [join(scriptsDir, 'update-readme.js')], { stdio: 'inherit' })
50
+ },
51
+
52
+ 'push-release': () => {
53
+ spawnSync('node', [join(scriptsDir, 'push-release-to-main.js')], { stdio: 'inherit' })
54
+ },
55
+
56
+ bugs: () => {
57
+ spawnSync('gh', ['issue', 'list', '--label', 'bug', '--state', 'open'], { stdio: 'inherit', shell: true })
58
+ },
59
+
60
+ 'create-bug': () => {
61
+ if (commandArgs.length === 0) {
62
+ spawnSync('node', [join(scriptsDir, 'create-bug.js')], { stdio: 'inherit' })
63
+ } else {
64
+ const title = commandArgs.join(' ')
65
+ spawnSync('gh', ['issue', 'create', '--label', 'bug', '--title', title], { stdio: 'inherit', shell: true })
66
+ }
67
+ },
68
+
69
+ tasks: () => {
70
+ spawnSync('gh', ['issue', 'list', '--label', 'task', '--state', 'open'], { stdio: 'inherit', shell: true })
71
+ },
72
+
73
+ 'create-task': () => {
74
+ if (commandArgs.length === 0) {
75
+ spawnSync('node', [join(scriptsDir, 'create-task.js')], { stdio: 'inherit' })
76
+ } else {
77
+ const title = commandArgs.join(' ')
78
+ spawnSync('gh', ['issue', 'create', '--label', 'task', '--title', title], { stdio: 'inherit', shell: true })
79
+ }
80
+ },
81
+
82
+ 'all-issues': () => {
83
+ spawnSync('gh', ['issue', 'list', '--state', 'open'], { stdio: 'inherit', shell: true })
84
+ },
85
+ }
86
+
87
+ if (commands[command]) {
88
+ commands[command]()
89
+ } else {
90
+ console.log(`
91
+ ⚡ JS Template CLI
92
+
93
+ Использование: jst <команда> [аргументы]
94
+
95
+ 📋 ПРОЕКТ:
96
+ jst init Инициализация проекта
97
+
98
+ 🔧 РАЗРАБОТКА:
99
+ jst changelog Создать changelog
100
+ jst release Полный релиз (проверка + changelog + README)
101
+ jst update-readme Обновить README
102
+ jst push-release Запушить релиз в main
103
+
104
+ 📝 ЗАДАЧИ:
105
+ jst tasks Список открытых задач
106
+ jst create-task [название] Создать задачу
107
+ jst bugs Список открытых багов
108
+ jst create-bug [название] Создать баг
109
+ jst all-issues Все открытые issues
110
+
111
+ 📚 ПРИМЕРЫ:
112
+ jst init
113
+ jst create-task "Добавить темную тему"
114
+ jst release
115
+ jst tasks
116
+ `)
117
+ process.exit(command ? 1 : 0)
118
+ }
package/bin/init.js ADDED
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env node
2
+ import { fileURLToPath } from 'url'
3
+ import { dirname, join } from 'path'
4
+ import { copyFileSync, mkdirSync, existsSync, readFileSync, writeFileSync, readdirSync, statSync } from 'fs'
5
+ import { execSync } from 'child_process'
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url))
8
+ const templateDir = join(__dirname, '../templates')
9
+ const targetDir = process.cwd()
10
+
11
+ console.log('⚡ JS Template by @vv0rkz — Инициализация проекта\n')
12
+
13
+ // 1. Копирование конфигов
14
+ console.log('📋 Копирование конфигов...')
15
+ const filesToCopy = ['.gitignore', 'changelog.config.js', 'commitlint.config.js']
16
+
17
+ filesToCopy.forEach((file) => {
18
+ const src = join(templateDir, file)
19
+ const dest = join(targetDir, file)
20
+
21
+ if (existsSync(dest)) {
22
+ console.log(` ⚠️ ${file} уже существует, пропускаем`)
23
+ } else {
24
+ copyFileSync(src, dest)
25
+ console.log(` ✅ ${file}`)
26
+ }
27
+ })
28
+
29
+ // 2. Копирование .husky
30
+ console.log('\n🐶 Настройка husky хуков...')
31
+ const huskyDir = join(targetDir, '.husky')
32
+ if (!existsSync(huskyDir)) {
33
+ mkdirSync(huskyDir, { recursive: true })
34
+ }
35
+
36
+ // Копирование всей папки .husky
37
+ const huskyTemplateDir = join(templateDir, '.husky')
38
+ if (existsSync(huskyTemplateDir)) {
39
+ const copyDir = (src, dest) => {
40
+ if (!existsSync(dest)) {
41
+ mkdirSync(dest, { recursive: true })
42
+ }
43
+
44
+ const entries = readdirSync(src)
45
+ for (const entry of entries) {
46
+ const srcPath = join(src, entry)
47
+ const destPath = join(dest, entry)
48
+
49
+ if (statSync(srcPath).isDirectory()) {
50
+ copyDir(srcPath, destPath)
51
+ } else {
52
+ copyFileSync(srcPath, destPath)
53
+ }
54
+ }
55
+ }
56
+
57
+ copyDir(huskyTemplateDir, huskyDir)
58
+ console.log(' ✅ Хуки скопированы')
59
+ }
60
+
61
+ // 3. Копирование tools-gh
62
+ console.log('\n🔧 Копирование GitHub скриптов...')
63
+ const toolsDir = join(targetDir, 'tools-gh')
64
+ if (!existsSync(toolsDir)) {
65
+ mkdirSync(toolsDir, { recursive: true })
66
+ }
67
+
68
+ const toolsSourceDir = join(__dirname, '../tools-gh')
69
+ if (existsSync(toolsSourceDir)) {
70
+ const toolFiles = readdirSync(toolsSourceDir)
71
+ toolFiles.forEach((file) => {
72
+ const src = join(toolsSourceDir, file)
73
+ const dest = join(toolsDir, file)
74
+ if (statSync(src).isFile()) {
75
+ copyFileSync(src, dest)
76
+ console.log(` ✅ ${file}`)
77
+ }
78
+ })
79
+ }
80
+
81
+ // 4. Добавление скриптов в package.json
82
+ console.log('\n📦 Обновление package.json...')
83
+ const packageJsonPath = join(targetDir, 'package.json')
84
+ if (existsSync(packageJsonPath)) {
85
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'))
86
+
87
+ // Добавляем только минимум скриптов
88
+ packageJson.scripts = {
89
+ ...packageJson.scripts,
90
+ prepare: 'husky',
91
+ jst: 'jst',
92
+ _: 'jst',
93
+ }
94
+
95
+ // Добавляем зависимость
96
+ if (!packageJson.devDependencies) {
97
+ packageJson.devDependencies = {}
98
+ }
99
+ packageJson.devDependencies['@vv0rkz/js-template'] = '^1.0.0'
100
+
101
+ writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2))
102
+ console.log(' ✅ Скрипты добавлены')
103
+ console.log(' npm run jst или npm run _')
104
+ }
105
+
106
+ // 5. Установка зависимостей
107
+ console.log('\n📥 Установка зависимостей...')
108
+ try {
109
+ execSync('npm install --save-dev @commitlint/cli @commitlint/config-conventional husky changelogen', {
110
+ stdio: 'inherit',
111
+ cwd: targetDir,
112
+ })
113
+ console.log(' ✅ Зависимости установлены')
114
+ } catch (error) {
115
+ console.error(' ❌ Ошибка установки зависимостей')
116
+ }
117
+
118
+ // 6. Инициализация husky
119
+ console.log('\n🔗 Активация husky...')
120
+ try {
121
+ execSync('npx husky init', { stdio: 'inherit', cwd: targetDir })
122
+ console.log(' ✅ Husky активирован')
123
+ } catch (error) {
124
+ console.log(' ⚠️ Husky уже инициализирован')
125
+ }
126
+
127
+ console.log(`
128
+ 🎉 JS Template успешно установлен!
129
+
130
+ 📖 БЫСТРЫЕ КОМАНДЫ:
131
+ npm run jst changelog # или: npm run _ changelog
132
+ npm run jst release # или: npm run _ release
133
+ npm run jst tasks # или: npm run _ tasks
134
+ npm run jst create-task # или: npm run _ create-task
135
+
136
+ 📚 ПОЛНЫЙ СПИСОК:
137
+ npm run jst
138
+
139
+ 🚀 Начни работу:
140
+ npm run _ create-task "Моя первая задача"
141
+ `)
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@vv0rkz/js-template",
3
+ "version": "1.0.0",
4
+ "description": "Reusable setup for JS projects with husky, changelog, gh tools",
5
+ "type": "module",
6
+ "bin": {
7
+ "jst": "./bin/cli.js",
8
+ "js-template": "./bin/cli.js"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "templates",
13
+ "tools-gh"
14
+ ],
15
+ "scripts": {
16
+ "test": "echo 'Tests not implemented yet' && exit 0"
17
+ },
18
+ "keywords": [
19
+ "template",
20
+ "husky",
21
+ "changelog",
22
+ "github",
23
+ "workflow",
24
+ "commitlint"
25
+ ],
26
+ "author": "vv0rkz",
27
+ "license": "MIT",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/vv0rkz/js-template.git"
31
+ },
32
+ "dependencies": {
33
+ "@commitlint/cli": "^20.1.0",
34
+ "@commitlint/config-conventional": "^20.0.0",
35
+ "changelogen": "^0.6.2",
36
+ "husky": "^9.1.7"
37
+ },
38
+ "devDependencies": {
39
+ "browser-sync": "^3.0.4"
40
+ }
41
+ }
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync, existsSync } from "fs"
3
+ import { execSync } from "child_process"
4
+
5
+ // Читаем текущую версию
6
+ const packageJson = JSON.parse(readFileSync("package.json", "utf8"))
7
+ const [major, minor, patch] = packageJson.version.split(".").map(Number)
8
+
9
+ // Анализируем коммиты чтобы определить тип версии
10
+ const commitMessages = execSync("git log --oneline -10", { encoding: "utf8" })
11
+
12
+ let nextVersion
13
+ if (commitMessages.includes("feat:")) {
14
+ nextVersion = `v${major}.${minor + 1}.0` // minor release
15
+ } else {
16
+ nextVersion = `v${major}.${minor}.${patch + 1}` // patch release
17
+ }
18
+
19
+ console.log(`📦 Предполагаемая следующая версия: ${nextVersion}`)
20
+
21
+ // Проверяем демо
22
+ const hasDemo = existsSync(`docs/${nextVersion}.gif`) || existsSync(`docs/${nextVersion}.png`)
23
+
24
+ if (!hasDemo) {
25
+ console.log(`❌ Релиз ${nextVersion} требует демо!`)
26
+ console.log(`📸 Создай: docs/${nextVersion}.gif`)
27
+ process.exit(1)
28
+ }
29
+
30
+ console.log(`✅ Демо для ${nextVersion} готово!`)
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ import { execSync } from 'child_process'
3
+
4
+ const args = process.argv.slice(2)
5
+ const title = args.join(' ')
6
+
7
+ if (!title) {
8
+ console.log('❌ Использование: jst create-bug "описание бага"')
9
+ console.log(' или: npm run _ create-bug "описание бага"')
10
+ process.exit(1)
11
+ }
12
+
13
+ try {
14
+ // Создаем issue в GitHub
15
+ execSync(`gh issue create --title "Bug: ${title}" --body "Баг обнаружен" --label "bug" --assignee "@me"`, {
16
+ stdio: 'inherit',
17
+ })
18
+
19
+ console.log('🐛 Баг зарегистрирован!')
20
+ } catch (error) {
21
+ console.error('❌ Ошибка создания бага:', error.message)
22
+ console.log('\n💡 Убедись что:')
23
+ console.log(' 1. Установлен GitHub CLI: gh --version')
24
+ console.log(' 2. Выполнена авторизация: gh auth login')
25
+ process.exit(1)
26
+ }
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ import { execSync } from 'child_process'
3
+
4
+ const args = process.argv.slice(2)
5
+ const title = args.join(' ')
6
+
7
+ if (!title) {
8
+ console.log('❌ Использование: jst create-task "описание задачи"')
9
+ console.log(' или: npm run _ create-task "описание задачи"')
10
+ process.exit(1)
11
+ }
12
+
13
+ try {
14
+ console.log('📝 Создаю задачу...')
15
+ execSync(`gh issue create --title "Task: ${title}" --body "Задача: ${title}" --label "task"`, {
16
+ stdio: 'inherit',
17
+ })
18
+
19
+ console.log('✅ Задача создана! Используй номер в коммитах: feat: #номер описание')
20
+ } catch (error) {
21
+ console.error('❌ Ошибка создания задачи:', error.message)
22
+ console.log('\n💡 Убедись что:')
23
+ console.log(' 1. Установлен GitHub CLI: gh --version')
24
+ console.log(' 2. Выполнена авторизация: gh auth login')
25
+ process.exit(1)
26
+ }
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env node
2
+ import { execSync } from 'child_process'
3
+
4
+ console.log('🚀 Мердж релизной ветки в main...')
5
+
6
+ try {
7
+ // 1. Получаем текущую ветку
8
+ const currentBranch = execSync('git branch --show-current').toString().trim()
9
+
10
+ if (currentBranch === 'main' || currentBranch === 'master') {
11
+ console.log('⚠️ Вы уже находитесь на главной ветке!')
12
+ process.exit(0)
13
+ }
14
+
15
+ console.log(`📁 Текущая ветка: ${currentBranch}`)
16
+
17
+ // 2. Проверяем есть ли изменения для коммита
18
+ const status = execSync('git status --porcelain').toString().trim()
19
+ if (status) {
20
+ console.log('❌ Есть незакоммиченные изменения!')
21
+ console.log(" Сначала сделай коммит: git add . && git commit -m 'your message'")
22
+ process.exit(1)
23
+ }
24
+
25
+ // 3. Определяем главную ветку (main или master)
26
+ let mainBranch = 'main'
27
+ try {
28
+ execSync('git rev-parse --verify main', { stdio: 'ignore' })
29
+ } catch {
30
+ mainBranch = 'master'
31
+ }
32
+
33
+ // 4. Мерджим в main/master
34
+ console.log(`🔀 Переключаемся на ${mainBranch} и мерджим...`)
35
+ execSync(`git checkout ${mainBranch}`, { stdio: 'inherit' })
36
+ execSync(`git merge ${currentBranch} --no-ff -m "Release ${currentBranch}"`, { stdio: 'inherit' })
37
+
38
+ // 5. Пушим всё
39
+ console.log('📤 Пушим изменения...')
40
+ execSync(`git push origin ${mainBranch}`, { stdio: 'inherit' })
41
+ execSync('git push --tags', { stdio: 'inherit' })
42
+
43
+ console.log(`✅ Релиз из ветки ${currentBranch} завершён!`)
44
+ console.log(`💡 Теперь можешь удалить ветку: git branch -d ${currentBranch}`)
45
+ } catch (error) {
46
+ console.error('❌ Ошибка при мердже:', error.message)
47
+ process.exit(1)
48
+ }
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync, writeFileSync, existsSync } from 'fs'
3
+ import { execSync } from 'child_process'
4
+
5
+ console.log('🎨 Обновляю README релизами с демо...')
6
+
7
+ // Проверяем наличие файлов
8
+ if (!existsSync('CHANGELOG.md')) {
9
+ console.log('❌ CHANGELOG.md не найден')
10
+ console.log('💡 Сначала запусти: npm run _ changelog')
11
+ process.exit(1)
12
+ }
13
+
14
+ if (!existsSync('README.md')) {
15
+ console.log('❌ README.md не найден')
16
+ process.exit(1)
17
+ }
18
+
19
+ const changelog = readFileSync('CHANGELOG.md', 'utf8')
20
+ let readme = readFileSync('README.md', 'utf8')
21
+
22
+ // Получаем информацию о репозитории
23
+ let repoUrl
24
+ try {
25
+ const remoteUrl = execSync('git config --get remote.origin.url').toString().trim()
26
+ // Преобразуем git@github.com:user/repo.git в https://github.com/user/repo
27
+ if (remoteUrl.includes('github.com')) {
28
+ repoUrl = remoteUrl.replace('git@github.com:', 'https://github.com/').replace('.git', '')
29
+ }
30
+ } catch (error) {
31
+ console.log('⚠️ Не удалось определить URL репозитория')
32
+ }
33
+
34
+ // Парсим changelog - ТОЛЬКО версии с демо
35
+ const versionBlocks = changelog.split('## v').slice(1)
36
+ let prettyChangelog = '## 📋 История версий\n\n'
37
+ const processedVersions = new Set()
38
+
39
+ versionBlocks.forEach((versionBlock) => {
40
+ const versionMatch = versionBlock.match(/^(\d+\.\d+\.\d+)/)
41
+ if (!versionMatch) return
42
+
43
+ const version = `v${versionMatch[1]}`
44
+ if (processedVersions.has(version)) return
45
+ processedVersions.add(version)
46
+
47
+ // ПРОВЕРЯЕМ ДЕМО - если нет демо, пропускаем
48
+ const hasDemo = existsSync(`docs/${version}.gif`) || existsSync(`docs/${version}.png`)
49
+ if (!hasDemo) {
50
+ console.log(`⏭️ Пропускаем ${version} - нет демо`)
51
+ return
52
+ }
53
+
54
+ // Пропускаем если нет фич
55
+ if (!versionBlock.includes('### ✨ Фичи') && !versionBlock.includes('### 🚀')) {
56
+ console.log(`⏭️ Пропускаем ${version} - нет фич`)
57
+ return
58
+ }
59
+
60
+ // Извлекаем фичи
61
+ const features = []
62
+ const lines = versionBlock.split('\n')
63
+ let inFeaturesSection = false
64
+
65
+ for (const line of lines) {
66
+ if (line.includes('### ✨ Фичи') || line.includes('### 🚀')) {
67
+ inFeaturesSection = true
68
+ continue
69
+ }
70
+ if (inFeaturesSection && line.includes('### ')) break
71
+ if (inFeaturesSection && line.trim().startsWith('-') && features.length < 3) {
72
+ const cleanFeature = line
73
+ .replace(/^- /, '')
74
+ .replace(/\(\[#\d+\]\([^)]+\)\)/g, '')
75
+ .replace(/\[#\d+\]\([^)]+\)/g, '')
76
+ .replace(/#\d+\s*/, '')
77
+ .replace(/\[[^\]]+\]\([^)]+\)/g, '')
78
+ .trim()
79
+
80
+ if (cleanFeature && !cleanFeature.toLowerCase().includes('тест') && cleanFeature.length > 10) {
81
+ features.push(cleanFeature)
82
+ }
83
+ }
84
+ }
85
+
86
+ if (features.length === 0) return
87
+
88
+ console.log(`✅ Добавляем ${version} - есть демо и ${features.length} фич`)
89
+
90
+ // Форматируем версию
91
+ prettyChangelog += `### 🟢 ${version}\n\n`
92
+
93
+ // Добавляем демо (гарантированно есть)
94
+ if (existsSync(`docs/${version}.gif`)) {
95
+ prettyChangelog += `**Демо работы** \n<img src="docs/${version}.gif" width="400" />\n\n`
96
+ } else {
97
+ prettyChangelog += `**Демо работы** \n<img src="docs/${version}.png" width="400" />\n\n`
98
+ }
99
+
100
+ // Добавляем функционал
101
+ prettyChangelog += `**Функционал:**\n`
102
+ features.forEach((feature) => {
103
+ prettyChangelog += `- ${feature}\n`
104
+ })
105
+
106
+ // Добавляем ссылку на релиз (если есть URL репозитория)
107
+ if (repoUrl) {
108
+ prettyChangelog += `\n**Релиз:** ${repoUrl}/releases/tag/${version}\n\n`
109
+ }
110
+
111
+ prettyChangelog += `---\n\n`
112
+ })
113
+
114
+ // Заменяем секцию между маркерами
115
+ if (readme.includes('<!-- AUTOGENERATED_SECTION START -->')) {
116
+ const startMarker = '<!-- AUTOGENERATED_SECTION START -->'
117
+ const endMarker = '<!-- AUTOGENERATED_SECTION END -->'
118
+
119
+ const startIndex = readme.indexOf(startMarker)
120
+ const endIndex = readme.indexOf(endMarker, startIndex + startMarker.length)
121
+
122
+ if (startIndex !== -1 && endIndex !== -1) {
123
+ readme = readme.substring(0, startIndex + startMarker.length) + '\n' + prettyChangelog + readme.substring(endIndex)
124
+ console.log('✅ Секция обновлена')
125
+ }
126
+ } else {
127
+ console.log('⚠️ Маркер <!-- AUTOGENERATED_SECTION START --> не найден в README.md')
128
+ console.log('💡 Добавь в README.md:')
129
+ console.log(' <!-- AUTOGENERATED_SECTION START -->')
130
+ console.log(' <!-- AUTOGENERATED_SECTION END -->')
131
+ process.exit(1)
132
+ }
133
+
134
+ // Сохраняем
135
+ writeFileSync('README.md', readme)
136
+ console.log('✅ README обновлён с релизами, у которых есть демо!')
137
+
138
+ // Пытаемся закоммитить и запушить
139
+ try {
140
+ const status = execSync('git status --porcelain README.md').toString().trim()
141
+ if (status) {
142
+ execSync('git add README.md', { stdio: 'inherit' })
143
+ execSync('git commit -m "docs: update README with demo releases"', { stdio: 'inherit' })
144
+ execSync('git push', { stdio: 'inherit' })
145
+ console.log('🚀 Изменения запушены!')
146
+ } else {
147
+ console.log('💡 README не изменился')
148
+ }
149
+ } catch (error) {
150
+ console.log('💡 README обновлён локально (не удалось запушить автоматически)')
151
+ }