@vv0rkz/js-template 1.6.4 → 1.8.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.
@@ -1,6 +1,13 @@
1
- export default {
2
- extends: ["@commitlint/config-conventional"],
3
- rules: {
4
- "type-enum": [2, "always", ["feat", "fix", "refactor", "build", "chore", "docs"]],
5
- },
6
- }
1
+ let types = ['feat', 'fix', 'refactor', 'build', 'chore', 'docs', 'perf']
2
+
3
+ try {
4
+ const { default: config } = await import('./jst.config.js')
5
+ if (config.commits?.types) types = config.commits.types
6
+ } catch {}
7
+
8
+ export default {
9
+ extends: ['@commitlint/config-conventional'],
10
+ rules: {
11
+ 'type-enum': [2, 'always', types],
12
+ },
13
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * JST Configuration
3
+ * Документация: https://github.com/vv0rkz/js-template
4
+ *
5
+ * Все значения ниже — дефолтные. Измени нужные или закомментируй.
6
+ */
7
+ export default {
8
+ /**
9
+ * Правила именования веток
10
+ */
11
+ branch: {
12
+ /** Имя главной ветки ('main' | 'master') */
13
+ main: 'main',
14
+
15
+ /**
16
+ * Допустимые форматы веток (массив шаблонов)
17
+ *
18
+ * Плейсхолдеры:
19
+ * {version} → семвер (X.Y.Z)
20
+ * {issue} → номер issue (цифры)
21
+ * {name} → описание (буквы, цифры, дефисы, подчёркивания)
22
+ * * → что угодно
23
+ *
24
+ * Примеры наборов:
25
+ *
26
+ * ['v{version}-{name}']
27
+ * ✅ v2.3.0-normalize-operators
28
+ *
29
+ * ['release/v{version}', 'feature/#{issue}-{name}', 'fix/#{issue}-{name}']
30
+ * ✅ release/v2.3.0
31
+ * ✅ feature/#12-dark-theme
32
+ * ✅ fix/#7-validation-bug
33
+ *
34
+ * ['feature/*', 'fix/*', 'release/*']
35
+ * ✅ feature/anything-goes-here
36
+ * ✅ fix/urgent-patch
37
+ *
38
+ * ['*']
39
+ * ✅ любое имя (без ограничений)
40
+ */
41
+ patterns: ['v{version}-{name}'],
42
+ },
43
+
44
+ /**
45
+ * GitHub Labels
46
+ * Используются при `jst setup-labels` и `jst create-*`
47
+ *
48
+ * Цвета — 6-символьный hex без #
49
+ */
50
+ labels: [
51
+ { name: 'task', color: '0E8A16', description: 'Новая фича', emoji: '✨' },
52
+ { name: 'bug', color: 'D73A4A', description: 'Баг', emoji: '🐛' },
53
+ { name: 'refactor', color: 'FEF2C0', description: 'Рефакторинг/техдолг', emoji: '♻️' },
54
+ { name: 'perf', color: '007bff', description: 'Оптимизация производительности', emoji: '⚡' },
55
+ ],
56
+
57
+ /**
58
+ * Правила коммитов
59
+ */
60
+ commits: {
61
+ /**
62
+ * Разрешённые типы коммитов
63
+ * Должны совпадать с commitlint и changelog
64
+ */
65
+ types: ['feat', 'fix', 'refactor', 'build', 'chore', 'docs', 'perf'],
66
+
67
+ /**
68
+ * Типы, требующие номер issue (#N)
69
+ * Остальные типы — свободный формат
70
+ */
71
+ requireIssue: ['feat', 'fix'],
72
+
73
+ /**
74
+ * Ключевое слово для закрытия issue в коммите
75
+ *
76
+ * С ним: feat: close #9 описание → закроет issue #9
77
+ * Без: feat: #9 описание → просто ссылка, issue остаётся открытым
78
+ */
79
+ closeKeyword: 'close',
80
+ },
81
+
82
+ /**
83
+ * Настройки релиза (`jst release`)
84
+ */
85
+ release: {
86
+ /** Требовать GIF/PNG демо перед релизом (true | false) */
87
+ requireDemo: true,
88
+
89
+ /** Директория для демо-файлов */
90
+ demoDir: 'docs',
91
+
92
+ /** Допустимые форматы демо-файлов */
93
+ demoFormats: ['gif', 'png'],
94
+ },
95
+
96
+ /**
97
+ * Настройки changelog (changelogen)
98
+ * title — заголовок секции в CHANGELOG.md
99
+ * semver — тип версионирования ('major' | 'minor' | 'patch')
100
+ */
101
+ changelog: {
102
+ types: {
103
+ feat: { title: '✨ Новые фичи', semver: 'minor' },
104
+ fix: { title: '🐛 Исправления', semver: 'patch' },
105
+ refactor: { title: '♻️ Рефакторинг', semver: 'patch' },
106
+ perf: { title: '⚡ Оптимизация', semver: 'patch' },
107
+ },
108
+ },
109
+
110
+ /**
111
+ * Автообновление зависимостей
112
+ * @type {'dependabot' | 'renovate' | false}
113
+ *
114
+ * 'dependabot' — создаст .github/dependabot.yml
115
+ * 'renovate' — создаст renovate.json
116
+ * false — отключено
117
+ */
118
+ depUpdater: false,
119
+
120
+ /**
121
+ * Репозиторий JST для команды `report-issue`
122
+ * Формат: 'owner/repo'
123
+ */
124
+ jstRepo: 'vv0rkz/js-template',
125
+ }
@@ -1,30 +1,36 @@
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} готово!`)
1
+ #!/usr/bin/env node
2
+ import { existsSync, readFileSync } from 'fs'
3
+ import { execSync } from 'child_process'
4
+ import { loadConfig } from './config.js'
5
+
6
+ const config = await loadConfig()
7
+
8
+ if (!config.release.requireDemo) {
9
+ console.log('⏭️ Проверка демо отключена')
10
+ process.exit(0)
11
+ }
12
+
13
+ const packageJson = JSON.parse(readFileSync('package.json', 'utf8'))
14
+ const [major, minor, patch] = packageJson.version.split('.').map(Number)
15
+
16
+ const commitMessages = execSync('git log --oneline -10', { encoding: 'utf8' })
17
+
18
+ let nextVersion
19
+ if (commitMessages.includes('feat:')) {
20
+ nextVersion = `v${major}.${minor + 1}.0`
21
+ } else {
22
+ nextVersion = `v${major}.${minor}.${patch + 1}`
23
+ }
24
+
25
+ console.log(`📦 Предполагаемая следующая версия: ${nextVersion}`)
26
+
27
+ const { demoDir, demoFormats } = config.release
28
+ const hasDemo = demoFormats.some((fmt) => existsSync(`${demoDir}/${nextVersion}.${fmt}`))
29
+
30
+ if (!hasDemo) {
31
+ console.log(`❌ Релиз ${nextVersion} требует демо!`)
32
+ console.log(`📸 Создай: ${demoDir}/${nextVersion}.${demoFormats[0]}`)
33
+ process.exit(1)
34
+ }
35
+
36
+ console.log(`✅ Демо для ${nextVersion} готово!`)
@@ -0,0 +1,126 @@
1
+ import { existsSync, readFileSync } from 'fs'
2
+ import { join } from 'path'
3
+ import { pathToFileURL } from 'url'
4
+
5
+ const DEFAULTS = {
6
+ branch: {
7
+ main: 'main',
8
+ patterns: ['v{version}-{name}'],
9
+ },
10
+ labels: [
11
+ { name: 'task', color: '0E8A16', description: 'Новая фича', emoji: '✨' },
12
+ { name: 'bug', color: 'D73A4A', description: 'Баг', emoji: '🐛' },
13
+ { name: 'refactor', color: 'FEF2C0', description: 'Рефакторинг/техдолг', emoji: '♻️' },
14
+ { name: 'perf', color: '007bff', description: 'Оптимизация производительности', emoji: '⚡' },
15
+ ],
16
+ commits: {
17
+ types: ['feat', 'fix', 'refactor', 'build', 'chore', 'docs', 'perf'],
18
+ requireIssue: ['feat', 'fix'],
19
+ closeKeyword: 'close',
20
+ },
21
+ release: {
22
+ requireDemo: true,
23
+ demoDir: 'docs',
24
+ demoFormats: ['gif', 'png'],
25
+ },
26
+ changelog: {
27
+ types: {
28
+ feat: { title: '✨ Новые фичи', semver: 'minor' },
29
+ fix: { title: '🐛 Исправления', semver: 'patch' },
30
+ refactor: { title: '♻️ Рефакторинг', semver: 'patch' },
31
+ perf: { title: '⚡ Оптимизация', semver: 'patch' },
32
+ },
33
+ },
34
+ depUpdater: false,
35
+ jstRepo: 'vv0rkz/js-template',
36
+ }
37
+
38
+ // --- Branch pattern template engine ---
39
+
40
+ const BRANCH_PLACEHOLDERS = {
41
+ '{version}': '\\d+\\.\\d+\\.\\d+',
42
+ '{issue}': '\\d+',
43
+ '{name}': '[a-zA-Z0-9_-]+',
44
+ }
45
+
46
+ function compileTemplate(template) {
47
+ const parts = template.split(/(\{[a-z]+\}|\*)/g)
48
+ return parts
49
+ .map((part) => {
50
+ if (BRANCH_PLACEHOLDERS[part]) return BRANCH_PLACEHOLDERS[part]
51
+ if (part === '*') return '.+'
52
+ return part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
53
+ })
54
+ .join('')
55
+ }
56
+
57
+ /**
58
+ * Compiles branch config into a RegExp.
59
+ * Supports template patterns (new) and raw regex (legacy).
60
+ *
61
+ * Templates:
62
+ * {version} → X.Y.Z (semver)
63
+ * {issue} → issue number (digits)
64
+ * {name} → branch description (letters, digits, hyphens, underscores)
65
+ * * → anything
66
+ */
67
+ export function compileBranchRegex(branchConfig) {
68
+ if (branchConfig.patterns && Array.isArray(branchConfig.patterns)) {
69
+ const compiled = branchConfig.patterns.map(compileTemplate)
70
+ return new RegExp(`^(${compiled.join('|')})$`)
71
+ }
72
+
73
+ if (branchConfig.pattern) {
74
+ return new RegExp(branchConfig.pattern)
75
+ }
76
+
77
+ return new RegExp('^v.*-.*')
78
+ }
79
+
80
+ // --- Deep merge ---
81
+
82
+ function deepMerge(target, source) {
83
+ const result = { ...target }
84
+ for (const key of Object.keys(source)) {
85
+ if (
86
+ source[key] &&
87
+ typeof source[key] === 'object' &&
88
+ !Array.isArray(source[key]) &&
89
+ target[key] &&
90
+ typeof target[key] === 'object' &&
91
+ !Array.isArray(target[key])
92
+ ) {
93
+ result[key] = deepMerge(target[key], source[key])
94
+ } else {
95
+ result[key] = source[key]
96
+ }
97
+ }
98
+ return result
99
+ }
100
+
101
+ /**
102
+ * Loads config from jst.config.js (preferred) or jst.config.json (fallback)
103
+ * Deep-merges user config with defaults
104
+ */
105
+ export async function loadConfig() {
106
+ const jsPath = join(process.cwd(), 'jst.config.js')
107
+ const jsonPath = join(process.cwd(), 'jst.config.json')
108
+ let userConfig = {}
109
+
110
+ if (existsSync(jsPath)) {
111
+ try {
112
+ const mod = await import(pathToFileURL(jsPath).href)
113
+ userConfig = mod.default || {}
114
+ } catch (e) {
115
+ console.warn('⚠️ Ошибка чтения jst.config.js:', e.message)
116
+ }
117
+ } else if (existsSync(jsonPath)) {
118
+ try {
119
+ userConfig = JSON.parse(readFileSync(jsonPath, 'utf8'))
120
+ } catch (e) {
121
+ console.warn('⚠️ Ошибка чтения jst.config.json:', e.message)
122
+ }
123
+ }
124
+
125
+ return deepMerge(DEFAULTS, userConfig)
126
+ }
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+ import { execSync } from 'child_process'
3
+ import { loadConfig } from './config.js'
4
+
5
+ const config = await loadConfig()
6
+ const { closeKeyword, requireIssue } = config.commits
7
+
8
+ const commitMsg = execSync('git log -1 --pretty=%B', { encoding: 'utf8' }).trim()
9
+ const commitHash = execSync('git rev-parse --short HEAD', { encoding: 'utf8' }).trim()
10
+
11
+ let repoUrl = execSync('git remote get-url origin', { encoding: 'utf8' }).trim()
12
+ if (repoUrl.startsWith('git@github.com:')) {
13
+ repoUrl = repoUrl.replace('git@github.com:', 'https://github.com/').replace(/\.git$/, '')
14
+ } else {
15
+ repoUrl = repoUrl.replace(/\.git$/, '')
16
+ }
17
+
18
+ execSync('git push origin HEAD', { stdio: 'inherit' })
19
+
20
+ const escClose = closeKeyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
21
+ const typesGroup = requireIssue.join('|')
22
+ const closePattern = new RegExp(`^(${typesGroup})(\\(.+\\))?: ${escClose} #(\\d+)`)
23
+ const match = commitMsg.match(closePattern)
24
+
25
+ if (match) {
26
+ const issueNumber = match[3]
27
+ const commitLink = `${repoUrl}/commit/${commitHash}`
28
+ const firstLine = commitMsg.split('\n')[0]
29
+
30
+ console.log(`🔧 Закрываю issue #${issueNumber}...`)
31
+
32
+ try {
33
+ execSync(
34
+ `gh issue close "${issueNumber}" --comment "✅ Завершено в коммите: [${commitHash}](${commitLink}) - ${firstLine}"`,
35
+ { stdio: 'inherit' },
36
+ )
37
+ } catch {
38
+ console.log(`⚠️ Не удалось закрыть issue #${issueNumber}`)
39
+ }
40
+ }
@@ -1,102 +1,120 @@
1
- #!/usr/bin/env node
2
- import { execSync, spawnSync } from 'child_process'
3
- import { platform } from 'os'
4
- import { dirname, join } from 'path'
5
- import { fileURLToPath } from 'url'
6
-
7
- const __dirname = dirname(fileURLToPath(import.meta.url))
8
- const isWin = platform() === 'win32'
9
- const npxCmd = isWin ? 'npx.cmd' : 'npx'
10
-
11
- console.log('🚀 Запуск релиза...\n')
12
-
13
- // 1. Проверка демо
14
- const checkDemo = spawnSync('node', [join(__dirname, 'check-demo-for-release.js')], { stdio: 'inherit' })
15
- if (checkDemo.status !== 0) {
16
- console.error('❌ Проверка демо не прошла')
17
- process.exit(1)
18
- }
19
-
20
- // 2. Получаем текущую версию
21
- const currentVersion = JSON.parse(execSync('npm pkg get version', { encoding: 'utf8' })).replace(/"/g, '')
22
- console.log(`📦 Текущая версия: ${currentVersion}`)
23
-
24
- // 3. Предупреждение для 0.x.x
25
- if (currentVersion.startsWith('0.')) {
26
- console.log('\n⚠️ ВНИМАНИЕ: Версия 0.x.x имеет особое поведение!')
27
- console.log('💡 changelogen для версий < 1.0.0:')
28
- console.log(' • feat: коммиты patch bump (0.1.0 → 0.1.1)')
29
- console.log(' • fix: коммиты → patch bump (0.1.0 → 0.1.1)')
30
- console.log(' • BREAKING CHANGE minor bump (0.1.0 → 0.2.0)')
31
- console.log('')
32
- console.log('📖 Подробнее: https://github.com/conventional-changelog/standard-version/issues/539')
33
- console.log('✅ Рекомендация: используй версии ≥ 1.0.0 для правильного semver')
34
- console.log(' Обнови: npm pkg set version=1.0.0 && git tag v1.0.0\n')
35
- }
36
-
37
- // 4. Анализ коммитов
38
- let lastTag = ''
39
- try {
40
- lastTag = execSync('git describe --tags --abbrev=0', { encoding: 'utf8' }).trim()
41
- console.log(`📌 Последний тег: ${lastTag}`)
42
- } catch (error) {
43
- console.log('📌 Теги не найдены (первый релиз)')
44
- }
45
-
46
- const commitMessages = lastTag
47
- ? execSync(`git log ${lastTag}..HEAD --format=%s`, { encoding: 'utf8' })
48
- : execSync('git log --format=%s -10', { encoding: 'utf8' })
49
-
50
- const commits = commitMessages.split('\n').filter(Boolean)
51
-
52
- const featCount = commits.filter((c) => c.startsWith('feat:')).length
53
- const fixCount = commits.filter((c) => c.startsWith('fix:')).length
54
- const refactorCount = commits.filter((c) => c.startsWith('refactor:')).length
55
- const perfCount = commits.filter((c) => c.startsWith('perf:')).length
56
- const docsCount = commits.filter((c) => c.startsWith('docs:')).length
57
- const buildCount = commits.filter((c) => c.startsWith('build:')).length
58
-
59
- console.log('\n📊 Анализ коммитов:')
60
- if (featCount > 0) console.log(` ✨ feat: ${featCount}`)
61
- if (fixCount > 0) console.log(` 🐛 fix: ${fixCount}`)
62
- if (refactorCount > 0) console.log(` ♻️ refactor: ${refactorCount}`)
63
- if (perfCount > 0) console.log(` ⚡ perf: ${perfCount}`)
64
- if (docsCount > 0) console.log(` 📚 docs: ${docsCount}`)
65
- if (buildCount > 0) console.log(` 🏗️ build: ${buildCount}`)
66
-
67
- // 5. Определяем bump type
68
- const isV0 = currentVersion.startsWith('0.')
69
- let bumpType = 'patch'
70
-
71
- if (isV0) {
72
- // Для 0.x.x используем --major чтобы получить minor bump при feat
73
- bumpType = featCount > 0 ? 'major' : 'patch'
74
- console.log(`\n🔢 Bump type: ${bumpType === 'major' ? 'major (для 0.x.x это даст minor)' : 'patch'}`)
75
- } else {
76
- // Для ≥1.0.0 нормальное поведение
77
- bumpType = featCount > 0 ? 'minor' : 'patch'
78
- console.log(`\n🔢 Bump type: ${bumpType}`)
79
- }
80
-
81
- // 6. Создаём changelog
82
- console.log('\n📝 Создание changelog...')
83
- const changelog = spawnSync(npxCmd, ['changelogen', '--release', `--${bumpType}`], {
84
- stdio: 'inherit',
85
- })
86
-
87
- if (changelog.status !== 0) {
88
- console.error('\n❌ Ошибка создания changelog')
89
- console.log('💡 Попробуй вручную: npx changelogen --release')
90
- process.exit(1)
91
- }
92
-
93
- // 7. Обновляем README
94
- console.log('\n📝 Обновление README...')
95
- const updateReadme = spawnSync('node', [join(__dirname, 'update-readme.js')], { stdio: 'inherit' })
96
- if (updateReadme.status !== 0) {
97
- console.log('⚠️ README не обновлён (возможно нет секции AUTOGENERATED)')
98
- }
99
-
100
- console.log('\n Релиз успешно создан!')
101
- console.log('💡 Теперь можно:')
102
- console.log(' npm run _ push-release # Создать PR и смерджить в main')
1
+ #!/usr/bin/env node
2
+ import { execSync, spawnSync } from 'child_process'
3
+ import { existsSync, readFileSync } from 'fs'
4
+ import { platform } from 'os'
5
+ import { dirname, join } from 'path'
6
+ import { fileURLToPath } from 'url'
7
+ import { loadConfig } from './config.js'
8
+
9
+ const __dirname = dirname(fileURLToPath(import.meta.url))
10
+ const isWin = platform() === 'win32'
11
+ const npxCmd = isWin ? 'npx.cmd' : 'npx'
12
+ const config = await loadConfig()
13
+
14
+ console.log('🚀 Запуск релиза...\n')
15
+
16
+ // 1. Current version
17
+ const currentVersion = JSON.parse(readFileSync('package.json', 'utf8')).version
18
+ console.log(`📦 Текущая версия: ${currentVersion}`)
19
+
20
+ // 2. Demo check (configurable)
21
+ if (config.release.requireDemo) {
22
+ const [major, minor, patch] = currentVersion.split('.').map(Number)
23
+ const recentCommits = execSync('git log --oneline -10', { encoding: 'utf8' })
24
+ const nextVersion = recentCommits.includes('feat:')
25
+ ? `v${major}.${minor + 1}.0`
26
+ : `v${major}.${minor}.${patch + 1}`
27
+
28
+ console.log(`📦 Предполагаемая следующая версия: ${nextVersion}`)
29
+
30
+ const { demoDir, demoFormats } = config.release
31
+ const hasDemo = demoFormats.some((fmt) => existsSync(`${demoDir}/${nextVersion}.${fmt}`))
32
+
33
+ if (!hasDemo) {
34
+ console.log(`❌ Релиз ${nextVersion} требует демо!`)
35
+ console.log(`📸 Создай: ${demoDir}/${nextVersion}.${demoFormats[0]}`)
36
+ process.exit(1)
37
+ }
38
+
39
+ console.log(`✅ Демо для ${nextVersion} готово!`)
40
+ } else {
41
+ console.log('⏭️ Проверка демо отключена (release.requireDemo = false)')
42
+ }
43
+
44
+ // 3. Warning for 0.x.x
45
+ if (currentVersion.startsWith('0.')) {
46
+ console.log('\n⚠️ ВНИМАНИЕ: Версия 0.x.x имеет особое поведение!')
47
+ console.log('💡 changelogen для версий < 1.0.0:')
48
+ console.log(' feat: коммиты patch bump (0.1.0 → 0.1.1)')
49
+ console.log(' • fix: коммиты → patch bump (0.1.0 → 0.1.1)')
50
+ console.log(' • BREAKING CHANGE → minor bump (0.1.0 → 0.2.0)')
51
+ console.log('')
52
+ console.log('✅ Рекомендация: используй версии ≥ 1.0.0 для правильного semver')
53
+ console.log(' Обнови: npm pkg set version=1.0.0 && git tag v1.0.0\n')
54
+ }
55
+
56
+ // 4. Commit analysis
57
+ let lastTag = ''
58
+ try {
59
+ lastTag = execSync('git describe --tags --abbrev=0', { encoding: 'utf8' }).trim()
60
+ console.log(`📌 Последний тег: ${lastTag}`)
61
+ } catch {
62
+ console.log('📌 Теги не найдены (первый релиз)')
63
+ }
64
+
65
+ const commitLog = lastTag
66
+ ? execSync(`git log ${lastTag}..HEAD --format=%s`, { encoding: 'utf8' })
67
+ : execSync('git log --format=%s -10', { encoding: 'utf8' })
68
+
69
+ const commits = commitLog.split('\n').filter(Boolean)
70
+ const countByType = (prefix) => commits.filter((c) => c.startsWith(`${prefix}:`)).length
71
+
72
+ const featCount = countByType('feat')
73
+ const fixCount = countByType('fix')
74
+ const refactorCount = countByType('refactor')
75
+ const perfCount = countByType('perf')
76
+ const docsCount = countByType('docs')
77
+ const buildCount = countByType('build')
78
+
79
+ console.log('\n📊 Анализ коммитов:')
80
+ if (featCount) console.log(` ✨ feat: ${featCount}`)
81
+ if (fixCount) console.log(` 🐛 fix: ${fixCount}`)
82
+ if (refactorCount) console.log(` ♻️ refactor: ${refactorCount}`)
83
+ if (perfCount) console.log(` ⚡ perf: ${perfCount}`)
84
+ if (docsCount) console.log(` 📚 docs: ${docsCount}`)
85
+ if (buildCount) console.log(` 🏗️ build: ${buildCount}`)
86
+
87
+ // 5. Determine bump type
88
+ const isV0 = currentVersion.startsWith('0.')
89
+ let bumpType
90
+
91
+ if (isV0) {
92
+ bumpType = featCount > 0 ? 'major' : 'patch'
93
+ console.log(`\n🔢 Bump: ${bumpType === 'major' ? 'major (для 0.x.x это даст minor)' : 'patch'}`)
94
+ } else {
95
+ bumpType = featCount > 0 ? 'minor' : 'patch'
96
+ console.log(`\n🔢 Bump: ${bumpType}`)
97
+ }
98
+
99
+ // 6. Changelog
100
+ console.log('\n📝 Создание changelog...')
101
+ const changelog = spawnSync(npxCmd, ['changelogen', '--release', `--${bumpType}`], {
102
+ stdio: 'inherit',
103
+ })
104
+
105
+ if (changelog.status !== 0) {
106
+ console.error('\n❌ Ошибка создания changelog')
107
+ console.log('💡 Попробуй вручную: npx changelogen --release')
108
+ process.exit(1)
109
+ }
110
+
111
+ // 7. Update README
112
+ console.log('\n📝 Обновление README...')
113
+ const updateReadme = spawnSync('node', [join(__dirname, 'update-readme.js')], { stdio: 'inherit' })
114
+ if (updateReadme.status !== 0) {
115
+ console.log('⚠️ README не обновлён (возможно нет секции AUTOGENERATED)')
116
+ }
117
+
118
+ console.log('\n✅ Релиз успешно создан!')
119
+ console.log('💡 Теперь можно:')
120
+ console.log(' npm run _ push-release # Создать PR и смерджить в main')
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from 'child_process'
3
+ import { platform } from 'os'
4
+ import { loadConfig } from './config.js'
5
+
6
+ const config = await loadConfig()
7
+ const jstRepo = config.jstRepo
8
+ const isWin = platform() === 'win32'
9
+ const ghCmd = isWin ? 'gh.exe' : 'gh'
10
+
11
+ const title = process.argv.slice(2).join(' ')
12
+
13
+ console.log(`📝 Создаю issue в ${jstRepo}...`)
14
+
15
+ if (!title) {
16
+ const result = spawnSync(ghCmd, ['issue', 'create', '--repo', jstRepo], { stdio: 'inherit' })
17
+ process.exit(result.status || 0)
18
+ }
19
+
20
+ const result = spawnSync(ghCmd, ['issue', 'create', '--repo', jstRepo, '--title', title], {
21
+ stdio: 'inherit',
22
+ })
23
+
24
+ if (result.status === 0) {
25
+ console.log('\n✅ Issue создан!')
26
+ } else {
27
+ console.error('❌ Ошибка создания issue')
28
+ console.log('\n💡 Убедись что:')
29
+ console.log(' 1. Установлен GitHub CLI: gh --version')
30
+ console.log(' 2. Выполнена авторизация: gh auth login')
31
+ process.exit(1)
32
+ }