@vv0rkz/js-template 1.7.0 → 1.8.2
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 +224 -150
- package/bin/cli.js +227 -196
- package/bin/init.js +244 -236
- package/package.json +41 -41
- package/templates/.husky/commit-msg +3 -67
- package/templates/.husky/post-commit +3 -25
- package/templates/.husky/pre-push +3 -17
- package/templates/changelog.config.js +16 -9
- package/templates/commitlint.config.js +13 -6
- package/templates/jst.config.js +120 -0
- package/tools-gh/check-demo-for-release.js +36 -30
- package/tools-gh/config.js +125 -0
- package/tools-gh/post-commit-hook.js +40 -0
- package/tools-gh/release.js +124 -102
- package/tools-gh/report-issue.js +30 -0
- package/tools-gh/setup-deps.js +59 -0
- package/tools-gh/setup-labels.js +53 -58
- package/tools-gh/update-readme.js +45 -8
- package/tools-gh/upgrade.js +72 -0
- package/tools-gh/validate-branch.js +33 -0
- package/tools-gh/validate-commit.js +86 -0
package/tools-gh/release.js
CHANGED
|
@@ -1,102 +1,124 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { execSync, spawnSync } from 'child_process'
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
console.log(`📦 Текущая версия: ${currentVersion}`)
|
|
23
|
-
|
|
24
|
-
//
|
|
25
|
-
if (
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
console.log(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
console.log(
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
if (
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
console.log('
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
console.log(
|
|
101
|
-
|
|
102
|
-
|
|
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
|
+
if (!currentVersion) {
|
|
19
|
+
console.log('❌ Нет поля version в package.json. Добавь: "version": "X.Y.Z"')
|
|
20
|
+
process.exit(1)
|
|
21
|
+
}
|
|
22
|
+
console.log(`📦 Текущая версия: ${currentVersion}`)
|
|
23
|
+
|
|
24
|
+
// 2. Demo check (configurable)
|
|
25
|
+
if (config.release.requireDemo) {
|
|
26
|
+
const [major, minor, patch] = currentVersion.split('.').map(Number)
|
|
27
|
+
const recentCommits = execSync('git log --oneline -10', { encoding: 'utf8' })
|
|
28
|
+
const nextVersion = recentCommits.includes('feat:')
|
|
29
|
+
? `v${major}.${minor + 1}.0`
|
|
30
|
+
: `v${major}.${minor}.${patch + 1}`
|
|
31
|
+
|
|
32
|
+
console.log(`📦 Предполагаемая следующая версия: ${nextVersion}`)
|
|
33
|
+
|
|
34
|
+
const { demoDir, demoFormats } = config.release
|
|
35
|
+
const hasDemo = demoFormats.some((fmt) => existsSync(`${demoDir}/${nextVersion}.${fmt}`))
|
|
36
|
+
|
|
37
|
+
if (!hasDemo) {
|
|
38
|
+
console.log(`❌ Релиз ${nextVersion} требует демо!`)
|
|
39
|
+
console.log(`📸 Создай: ${demoDir}/${nextVersion}.${demoFormats[0]}`)
|
|
40
|
+
process.exit(1)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
console.log(`✅ Демо для ${nextVersion} готово!`)
|
|
44
|
+
} else {
|
|
45
|
+
console.log('⏭️ Проверка демо отключена (release.requireDemo = false)')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 3. Warning for 0.x.x
|
|
49
|
+
if (currentVersion.startsWith('0.')) {
|
|
50
|
+
console.log('\n⚠️ ВНИМАНИЕ: Версия 0.x.x имеет особое поведение!')
|
|
51
|
+
console.log('💡 changelogen для версий < 1.0.0:')
|
|
52
|
+
console.log(' • feat: коммиты → patch bump (0.1.0 → 0.1.1)')
|
|
53
|
+
console.log(' • fix: коммиты → patch bump (0.1.0 → 0.1.1)')
|
|
54
|
+
console.log(' • BREAKING CHANGE → minor bump (0.1.0 → 0.2.0)')
|
|
55
|
+
console.log('')
|
|
56
|
+
console.log('✅ Рекомендация: используй версии ≥ 1.0.0 для правильного semver')
|
|
57
|
+
console.log(' Обнови: npm pkg set version=1.0.0 && git tag v1.0.0\n')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 4. Commit analysis
|
|
61
|
+
let lastTag = ''
|
|
62
|
+
try {
|
|
63
|
+
lastTag = execSync('git describe --tags --abbrev=0', { encoding: 'utf8' }).trim()
|
|
64
|
+
console.log(`📌 Последний тег: ${lastTag}`)
|
|
65
|
+
} catch {
|
|
66
|
+
console.log('📌 Теги не найдены (первый релиз)')
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const commitLog = lastTag
|
|
70
|
+
? execSync(`git log ${lastTag}..HEAD --format=%s`, { encoding: 'utf8' })
|
|
71
|
+
: execSync('git log --format=%s -10', { encoding: 'utf8' })
|
|
72
|
+
|
|
73
|
+
const commits = commitLog.split('\n').filter(Boolean)
|
|
74
|
+
const countByType = (prefix) => commits.filter((c) => c.startsWith(`${prefix}:`)).length
|
|
75
|
+
|
|
76
|
+
const featCount = countByType('feat')
|
|
77
|
+
const fixCount = countByType('fix')
|
|
78
|
+
const refactorCount = countByType('refactor')
|
|
79
|
+
const perfCount = countByType('perf')
|
|
80
|
+
const docsCount = countByType('docs')
|
|
81
|
+
const buildCount = countByType('build')
|
|
82
|
+
|
|
83
|
+
console.log('\n📊 Анализ коммитов:')
|
|
84
|
+
if (featCount) console.log(` ✨ feat: ${featCount}`)
|
|
85
|
+
if (fixCount) console.log(` 🐛 fix: ${fixCount}`)
|
|
86
|
+
if (refactorCount) console.log(` ♻️ refactor: ${refactorCount}`)
|
|
87
|
+
if (perfCount) console.log(` ⚡ perf: ${perfCount}`)
|
|
88
|
+
if (docsCount) console.log(` 📚 docs: ${docsCount}`)
|
|
89
|
+
if (buildCount) console.log(` 🏗️ build: ${buildCount}`)
|
|
90
|
+
|
|
91
|
+
// 5. Determine bump type
|
|
92
|
+
const isV0 = currentVersion.startsWith('0.')
|
|
93
|
+
let bumpType
|
|
94
|
+
|
|
95
|
+
if (isV0) {
|
|
96
|
+
bumpType = featCount > 0 ? 'major' : 'patch'
|
|
97
|
+
console.log(`\n🔢 Bump: ${bumpType === 'major' ? 'major (для 0.x.x это даст minor)' : 'patch'}`)
|
|
98
|
+
} else {
|
|
99
|
+
bumpType = featCount > 0 ? 'minor' : 'patch'
|
|
100
|
+
console.log(`\n🔢 Bump: ${bumpType}`)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 6. Changelog
|
|
104
|
+
console.log('\n📝 Создание changelog...')
|
|
105
|
+
const changelog = spawnSync(npxCmd, ['changelogen', '--release', `--${bumpType}`], {
|
|
106
|
+
stdio: 'inherit',
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
if (changelog.status !== 0) {
|
|
110
|
+
console.error('\n❌ Ошибка создания changelog')
|
|
111
|
+
console.log('💡 Попробуй вручную: npx changelogen --release')
|
|
112
|
+
process.exit(1)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 7. Update README
|
|
116
|
+
console.log('\n📝 Обновление README...')
|
|
117
|
+
const updateReadme = spawnSync('node', [join(__dirname, 'update-readme.js')], { stdio: 'inherit' })
|
|
118
|
+
if (updateReadme.status !== 0) {
|
|
119
|
+
console.log('⚠️ README не обновлён (возможно нет секции AUTOGENERATED)')
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
console.log('\n✅ Релиз успешно создан!')
|
|
123
|
+
console.log('💡 Теперь можно:')
|
|
124
|
+
console.log(' npm run _ push-release # Создать PR и смерджить в main')
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from 'child_process'
|
|
3
|
+
import { platform } from 'os'
|
|
4
|
+
|
|
5
|
+
const jstRepo = 'vv0rkz/js-template'
|
|
6
|
+
const isWin = platform() === 'win32'
|
|
7
|
+
const ghCmd = isWin ? 'gh.exe' : 'gh'
|
|
8
|
+
|
|
9
|
+
const title = process.argv.slice(2).join(' ')
|
|
10
|
+
|
|
11
|
+
console.log(`📝 Создаю issue в ${jstRepo}...`)
|
|
12
|
+
|
|
13
|
+
if (!title) {
|
|
14
|
+
const result = spawnSync(ghCmd, ['issue', 'create', '--repo', jstRepo], { stdio: 'inherit' })
|
|
15
|
+
process.exit(result.status || 0)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const result = spawnSync(ghCmd, ['issue', 'create', '--repo', jstRepo, '--title', title], {
|
|
19
|
+
stdio: 'inherit',
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
if (result.status === 0) {
|
|
23
|
+
console.log('\n✅ Issue создан!')
|
|
24
|
+
} else {
|
|
25
|
+
console.error('❌ Ошибка создания issue')
|
|
26
|
+
console.log('\n💡 Убедись что:')
|
|
27
|
+
console.log(' 1. Установлен GitHub CLI: gh --version')
|
|
28
|
+
console.log(' 2. Выполнена авторизация: gh auth login')
|
|
29
|
+
process.exit(1)
|
|
30
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync } from 'fs'
|
|
3
|
+
import { join } from 'path'
|
|
4
|
+
import { loadConfig } from './config.js'
|
|
5
|
+
|
|
6
|
+
const config = await loadConfig()
|
|
7
|
+
const { depUpdater } = config
|
|
8
|
+
|
|
9
|
+
if (!depUpdater) {
|
|
10
|
+
console.log('⏭️ Автообновление зависимостей отключено (depUpdater = false)')
|
|
11
|
+
process.exit(0)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (depUpdater === 'dependabot') {
|
|
15
|
+
const dir = join(process.cwd(), '.github')
|
|
16
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
|
|
17
|
+
|
|
18
|
+
const filePath = join(dir, 'dependabot.yml')
|
|
19
|
+
if (existsSync(filePath)) {
|
|
20
|
+
console.log('⏭️ .github/dependabot.yml уже существует')
|
|
21
|
+
process.exit(0)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
writeFileSync(
|
|
25
|
+
filePath,
|
|
26
|
+
`version: 2
|
|
27
|
+
updates:
|
|
28
|
+
- package-ecosystem: "npm"
|
|
29
|
+
directory: "/"
|
|
30
|
+
schedule:
|
|
31
|
+
interval: "weekly"
|
|
32
|
+
open-pull-requests-limit: 10
|
|
33
|
+
`,
|
|
34
|
+
)
|
|
35
|
+
console.log('✅ Создан .github/dependabot.yml')
|
|
36
|
+
} else if (depUpdater === 'renovate') {
|
|
37
|
+
const filePath = join(process.cwd(), 'renovate.json')
|
|
38
|
+
if (existsSync(filePath)) {
|
|
39
|
+
console.log('⏭️ renovate.json уже существует')
|
|
40
|
+
process.exit(0)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
writeFileSync(
|
|
44
|
+
filePath,
|
|
45
|
+
JSON.stringify(
|
|
46
|
+
{
|
|
47
|
+
$schema: 'https://docs.renovatebot.com/renovate-schema.json',
|
|
48
|
+
extends: ['config:base'],
|
|
49
|
+
},
|
|
50
|
+
null,
|
|
51
|
+
2,
|
|
52
|
+
) + '\n',
|
|
53
|
+
)
|
|
54
|
+
console.log('✅ Создан renovate.json')
|
|
55
|
+
} else {
|
|
56
|
+
console.log(`⚠️ Неизвестный depUpdater: "${depUpdater}"`)
|
|
57
|
+
console.log('💡 Допустимые значения: "dependabot", "renovate", false')
|
|
58
|
+
process.exit(1)
|
|
59
|
+
}
|
package/tools-gh/setup-labels.js
CHANGED
|
@@ -1,58 +1,53 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { execSync } from 'child_process'
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
console.log(`\n📊 Итого: создано ${created}, пропущено ${skipped}`)
|
|
55
|
-
|
|
56
|
-
if (created > 0) {
|
|
57
|
-
console.log('✅ Labels настроены!')
|
|
58
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from 'child_process'
|
|
3
|
+
import { loadConfig } from './config.js'
|
|
4
|
+
|
|
5
|
+
const config = await loadConfig()
|
|
6
|
+
|
|
7
|
+
console.log('🏷️ Настройка GitHub labels...\n')
|
|
8
|
+
|
|
9
|
+
const labels = config.labels
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
execSync('gh auth status', { stdio: 'ignore' })
|
|
13
|
+
} catch (error) {
|
|
14
|
+
console.error('❌ GitHub CLI не установлен или не авторизован')
|
|
15
|
+
console.log('💡 Установи: https://cli.github.com/')
|
|
16
|
+
console.log(' И выполни: gh auth login')
|
|
17
|
+
process.exit(1)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let existingLabels = []
|
|
21
|
+
try {
|
|
22
|
+
const output = execSync('gh label list --json name', { encoding: 'utf8' })
|
|
23
|
+
existingLabels = JSON.parse(output).map((l) => l.name)
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.log('⚠️ Не удалось получить список labels (возможно репозиторий пустой)')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let created = 0
|
|
29
|
+
let skipped = 0
|
|
30
|
+
|
|
31
|
+
for (const label of labels) {
|
|
32
|
+
if (existingLabels.includes(label.name)) {
|
|
33
|
+
console.log(` ⏭️ ${label.name} (уже существует)`)
|
|
34
|
+
skipped++
|
|
35
|
+
continue
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
execSync(`gh label create "${label.name}" --color "${label.color}" --description "${label.description}"`, {
|
|
40
|
+
stdio: 'ignore',
|
|
41
|
+
})
|
|
42
|
+
console.log(` ✅ ${label.name}`)
|
|
43
|
+
created++
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.log(` ⚠️ ${label.name} (ошибка создания)`)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
console.log(`\n📊 Итого: создано ${created}, пропущено ${skipped}`)
|
|
50
|
+
|
|
51
|
+
if (created > 0) {
|
|
52
|
+
console.log('✅ Labels настроены!')
|
|
53
|
+
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { execSync } from 'child_process'
|
|
2
|
+
import { execSync, spawnSync } from 'child_process'
|
|
3
3
|
import { existsSync, readFileSync, writeFileSync } from 'fs'
|
|
4
|
+
import { loadConfig } from './config.js'
|
|
5
|
+
|
|
6
|
+
const config = await loadConfig()
|
|
7
|
+
const demoDir = config.release.demoDir
|
|
4
8
|
|
|
5
9
|
console.log('🎨 Обновляю README релизами с демо...')
|
|
6
10
|
|
|
@@ -30,21 +34,49 @@ try {
|
|
|
30
34
|
console.log('⚠️ Не удалось определить URL репозитория')
|
|
31
35
|
}
|
|
32
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Пытается извлечь первый кадр GIF в PNG через ffmpeg.
|
|
39
|
+
* Возвращает true если PNG был создан, false если ffmpeg недоступен или ошибка.
|
|
40
|
+
*/
|
|
41
|
+
function tryGeneratePng(gifPath, pngPath) {
|
|
42
|
+
const result = spawnSync('ffmpeg', ['-i', gifPath, '-frames:v', '1', pngPath, '-y'], {
|
|
43
|
+
encoding: 'utf8',
|
|
44
|
+
})
|
|
45
|
+
if (result.status === 0 && existsSync(pngPath)) {
|
|
46
|
+
console.log(`🖼️ PNG превью создан: ${pngPath}`)
|
|
47
|
+
return true
|
|
48
|
+
}
|
|
49
|
+
return false
|
|
50
|
+
}
|
|
51
|
+
|
|
33
52
|
// Парсим changelog - ТОЛЬКО версии с демо
|
|
34
53
|
const versionBlocks = changelog.split('## v').slice(1)
|
|
35
54
|
let prettyChangelog = '## 📋 История версий\n\n'
|
|
36
55
|
const processedVersions = new Set()
|
|
37
56
|
|
|
38
57
|
versionBlocks.forEach((versionBlock) => {
|
|
39
|
-
|
|
58
|
+
// Поддержка заголовков вида "v1.4.0...v2.0.0" (changelogen диапазон) и обычных "v2.0.0"
|
|
59
|
+
const rangeMatch = versionBlock.match(/^\d+\.\d+\.\d+\.\.\.v(\d+\.\d+\.\d+)/)
|
|
60
|
+
const simpleMatch = versionBlock.match(/^(\d+\.\d+\.\d+)/)
|
|
61
|
+
const versionMatch = rangeMatch || simpleMatch
|
|
40
62
|
if (!versionMatch) return
|
|
41
63
|
|
|
42
64
|
const version = `v${versionMatch[1]}`
|
|
43
65
|
if (processedVersions.has(version)) return
|
|
44
66
|
processedVersions.add(version)
|
|
45
67
|
|
|
46
|
-
|
|
47
|
-
const
|
|
68
|
+
const gifPath = `${demoDir}/${version}.gif`
|
|
69
|
+
const pngPath = `${demoDir}/${version}.png`
|
|
70
|
+
|
|
71
|
+
const hasGif = existsSync(gifPath)
|
|
72
|
+
let hasPng = existsSync(pngPath)
|
|
73
|
+
|
|
74
|
+
// Если есть GIF но нет PNG — пытаемся сгенерировать PNG через ffmpeg
|
|
75
|
+
if (hasGif && !hasPng) {
|
|
76
|
+
hasPng = tryGeneratePng(gifPath, pngPath)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const hasDemo = hasGif || hasPng
|
|
48
80
|
if (!hasDemo) {
|
|
49
81
|
console.log(`⏭️ Пропускаем ${version} - нет демо`)
|
|
50
82
|
return
|
|
@@ -94,11 +126,16 @@ versionBlocks.forEach((versionBlock) => {
|
|
|
94
126
|
// Форматируем версию
|
|
95
127
|
prettyChangelog += `### 🟢 ${version}\n\n`
|
|
96
128
|
|
|
97
|
-
// Добавляем демо
|
|
98
|
-
|
|
99
|
-
|
|
129
|
+
// Добавляем демо
|
|
130
|
+
// Если есть GIF + PNG → кликабельный превью (быстрая загрузка + анимация по клику)
|
|
131
|
+
// Если только PNG → обычный img
|
|
132
|
+
// Если только GIF → обычный img с GIF
|
|
133
|
+
if (hasGif && hasPng) {
|
|
134
|
+
prettyChangelog += `**Демо работы** \n<a href="${gifPath}" title="Нажми чтобы увидеть анимацию"><img src="${pngPath}" width="400" /></a>\n\n`
|
|
135
|
+
} else if (hasPng) {
|
|
136
|
+
prettyChangelog += `**Демо работы** \n<img src="${pngPath}" width="400" />\n\n`
|
|
100
137
|
} else {
|
|
101
|
-
prettyChangelog += `**Демо работы** \n<img src="
|
|
138
|
+
prettyChangelog += `**Демо работы** \n<img src="${gifPath}" width="400" />\n\n`
|
|
102
139
|
}
|
|
103
140
|
|
|
104
141
|
// Добавляем функционал
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from 'fs'
|
|
3
|
+
import { spawnSync } from 'child_process'
|
|
4
|
+
import { dirname, join } from 'path'
|
|
5
|
+
import { fileURLToPath } from 'url'
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
8
|
+
const templateDir = join(__dirname, '../templates')
|
|
9
|
+
const targetDir = process.cwd()
|
|
10
|
+
|
|
11
|
+
console.log('⬆️ JST Upgrade — обновление конфигов и хуков\n')
|
|
12
|
+
|
|
13
|
+
// 1. jst.config.js — только если нет (не перезаписываем пользовательские настройки)
|
|
14
|
+
const configSrc = join(templateDir, 'jst.config.js')
|
|
15
|
+
const configDest = join(targetDir, 'jst.config.js')
|
|
16
|
+
|
|
17
|
+
if (!existsSync(configDest)) {
|
|
18
|
+
copyFileSync(configSrc, configDest)
|
|
19
|
+
console.log('✅ jst.config.js создан (настрой под свой проект)')
|
|
20
|
+
} else {
|
|
21
|
+
console.log('⏭️ jst.config.js уже существует (не перезаписан)')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// 2. Husky hooks — всегда обновляем (это тонкие обёртки, кастомизация в jst.config.js)
|
|
25
|
+
console.log('\n🐶 Обновление husky хуков...')
|
|
26
|
+
const huskyDir = join(targetDir, '.husky')
|
|
27
|
+
const huskyTemplateDir = join(templateDir, '.husky')
|
|
28
|
+
|
|
29
|
+
if (!existsSync(huskyDir)) mkdirSync(huskyDir, { recursive: true })
|
|
30
|
+
|
|
31
|
+
if (existsSync(huskyTemplateDir)) {
|
|
32
|
+
const copyDir = (src, dest) => {
|
|
33
|
+
if (!existsSync(dest)) mkdirSync(dest, { recursive: true })
|
|
34
|
+
for (const entry of readdirSync(src)) {
|
|
35
|
+
const srcPath = join(src, entry)
|
|
36
|
+
const destPath = join(dest, entry)
|
|
37
|
+
if (statSync(srcPath).isDirectory()) {
|
|
38
|
+
copyDir(srcPath, destPath)
|
|
39
|
+
} else {
|
|
40
|
+
copyFileSync(srcPath, destPath)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
copyDir(huskyTemplateDir, huskyDir)
|
|
45
|
+
console.log('✅ Хуки обновлены')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 3. commitlint + changelog — обновляем (кастомизация теперь через jst.config.js)
|
|
49
|
+
const configs = ['commitlint.config.js', 'changelog.config.js']
|
|
50
|
+
|
|
51
|
+
for (const name of configs) {
|
|
52
|
+
const src = join(templateDir, name)
|
|
53
|
+
const dest = join(targetDir, name)
|
|
54
|
+
if (existsSync(src)) {
|
|
55
|
+
copyFileSync(src, dest)
|
|
56
|
+
console.log(`✅ ${name} обновлён`)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 4. Применяем конфиг (labels + deps)
|
|
61
|
+
console.log('\n⚙️ Применение конфига...')
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
spawnSync('node', [join(__dirname, 'setup-labels.js')], { stdio: 'inherit' })
|
|
65
|
+
} catch {
|
|
66
|
+
console.log('⏭️ Labels пропущены (нет gh)')
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
spawnSync('node', [join(__dirname, 'setup-deps.js')], { stdio: 'inherit' })
|
|
70
|
+
|
|
71
|
+
console.log('\n✅ Upgrade завершён!')
|
|
72
|
+
console.log('💡 Проверь jst.config.js и настрой под свой проект')
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from 'child_process'
|
|
3
|
+
import { loadConfig, compileBranchRegex } from './config.js'
|
|
4
|
+
|
|
5
|
+
const config = await loadConfig()
|
|
6
|
+
const { main: mainBranch, patterns } = config.branch
|
|
7
|
+
|
|
8
|
+
const currentBranch = execSync('git branch --show-current', { encoding: 'utf8' }).trim()
|
|
9
|
+
|
|
10
|
+
if (currentBranch === mainBranch) {
|
|
11
|
+
console.log(`✅ Разрешённая ветка: ${currentBranch}`)
|
|
12
|
+
process.exit(0)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const branchRegex = compileBranchRegex(config.branch)
|
|
16
|
+
|
|
17
|
+
if (!branchRegex.test(currentBranch)) {
|
|
18
|
+
console.log(`❌ Неправильный формат ветки: ${currentBranch}`)
|
|
19
|
+
console.log('')
|
|
20
|
+
|
|
21
|
+
if (patterns && patterns.length) {
|
|
22
|
+
console.log('✅ Допустимые форматы:')
|
|
23
|
+
for (const p of patterns) {
|
|
24
|
+
console.log(` ${p}`)
|
|
25
|
+
}
|
|
26
|
+
} else {
|
|
27
|
+
console.log(`✅ Паттерн: ${config.branch.pattern}`)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
process.exit(1)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log(`✅ Формат ветки правильный: ${currentBranch}`)
|