@vv0rkz/js-template 1.8.2 → 1.8.4
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 +33 -6
- package/package.json +3 -2
- package/templates/jst.config.js +18 -6
- package/tools-gh/check-demo-for-release.js +2 -2
- package/tools-gh/config.js +37 -3
- package/tools-gh/release.js +3 -3
- package/tools-gh/update-readme.js +50 -42
package/README.md
CHANGED
|
@@ -52,16 +52,16 @@ export default {
|
|
|
52
52
|
|
|
53
53
|
// Релиз
|
|
54
54
|
release: {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
demo: {
|
|
56
|
+
enable: true, // требовать демо перед релизом
|
|
57
|
+
dir: 'docs', // где искать демо-файлы
|
|
58
|
+
formats: ['gif', 'png'], // допустимые форматы
|
|
59
|
+
style: 'click', // 'click' | 'side-by-side'
|
|
60
|
+
},
|
|
58
61
|
},
|
|
59
62
|
|
|
60
63
|
// Автообновление зависимостей
|
|
61
64
|
depUpdater: false, // 'dependabot' | 'renovate' | false
|
|
62
|
-
|
|
63
|
-
// Репозиторий JST для report-issue
|
|
64
|
-
jstRepo: 'vv0rkz/js-template',
|
|
65
65
|
}
|
|
66
66
|
```
|
|
67
67
|
|
|
@@ -219,6 +219,33 @@ npm run _ push-release
|
|
|
219
219
|
- [Changelogen](https://github.com/unjs/changelogen) — Генерация changelog
|
|
220
220
|
- [GitHub CLI](https://cli.github.com/) — Управление issues
|
|
221
221
|
|
|
222
|
+
## Migration Guide
|
|
223
|
+
|
|
224
|
+
### v1.9.0 — `release.demo` (breaking change)
|
|
225
|
+
|
|
226
|
+
Настройки демо переехали из плоского `release.*` в объект `release.demo`:
|
|
227
|
+
|
|
228
|
+
```js
|
|
229
|
+
// ❌ Было (до v1.9.0)
|
|
230
|
+
release: {
|
|
231
|
+
requireDemo: true,
|
|
232
|
+
demoDir: 'docs',
|
|
233
|
+
demoFormats: ['gif', 'png'],
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ✅ Стало
|
|
237
|
+
release: {
|
|
238
|
+
demo: {
|
|
239
|
+
enable: true,
|
|
240
|
+
dir: 'docs',
|
|
241
|
+
formats: ['gif', 'png'],
|
|
242
|
+
style: 'click', // новая опция: 'click' | 'side-by-side'
|
|
243
|
+
},
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Если в `jst.config.js` остались старые ключи, при запуске любой `jst`-команды появится предупреждение с инструкцией по миграции.
|
|
248
|
+
|
|
222
249
|
## Лицензия
|
|
223
250
|
|
|
224
251
|
MIT © [vv0rkz](https://github.com/vv0rkz)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vv0rkz/js-template",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.4",
|
|
4
4
|
"description": "Reusable setup for JS projects with husky, changelog, gh tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
"@commitlint/cli": "^20.1.0",
|
|
34
34
|
"@commitlint/config-conventional": "^20.0.0",
|
|
35
35
|
"changelogen": "^0.6.2",
|
|
36
|
-
"husky": "^9.1.7"
|
|
36
|
+
"husky": "^9.1.7",
|
|
37
|
+
"sharp": "^0.34.5"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
|
39
40
|
"browser-sync": "^3.0.4"
|
package/templates/jst.config.js
CHANGED
|
@@ -83,14 +83,26 @@ export default {
|
|
|
83
83
|
* Настройки релиза (`jst release`)
|
|
84
84
|
*/
|
|
85
85
|
release: {
|
|
86
|
-
/**
|
|
87
|
-
|
|
86
|
+
/**
|
|
87
|
+
* Настройки демо
|
|
88
|
+
*/
|
|
89
|
+
demo: {
|
|
90
|
+
/** Требовать демо перед релизом (true | false) */
|
|
91
|
+
enable: true,
|
|
88
92
|
|
|
89
|
-
|
|
90
|
-
|
|
93
|
+
/** Директория для демо-файлов */
|
|
94
|
+
dir: 'docs',
|
|
91
95
|
|
|
92
|
-
|
|
93
|
-
|
|
96
|
+
/** Допустимые форматы демо-файлов */
|
|
97
|
+
formats: ['gif', 'png'],
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Способ отображения демо в README
|
|
101
|
+
* 'click' — PNG превью, клик открывает GIF (быстрая загрузка)
|
|
102
|
+
* 'side-by-side' — PNG + GIF рядом (GIF грузится автоматически)
|
|
103
|
+
*/
|
|
104
|
+
style: 'click',
|
|
105
|
+
},
|
|
94
106
|
},
|
|
95
107
|
|
|
96
108
|
/**
|
|
@@ -5,7 +5,7 @@ import { loadConfig } from './config.js'
|
|
|
5
5
|
|
|
6
6
|
const config = await loadConfig()
|
|
7
7
|
|
|
8
|
-
if (!config.release.
|
|
8
|
+
if (!config.release.demo.enable) {
|
|
9
9
|
console.log('⏭️ Проверка демо отключена')
|
|
10
10
|
process.exit(0)
|
|
11
11
|
}
|
|
@@ -24,7 +24,7 @@ if (commitMessages.includes('feat:')) {
|
|
|
24
24
|
|
|
25
25
|
console.log(`📦 Предполагаемая следующая версия: ${nextVersion}`)
|
|
26
26
|
|
|
27
|
-
const { demoDir, demoFormats } = config.release
|
|
27
|
+
const { dir: demoDir, formats: demoFormats } = config.release.demo
|
|
28
28
|
const hasDemo = demoFormats.some((fmt) => existsSync(`${demoDir}/${nextVersion}.${fmt}`))
|
|
29
29
|
|
|
30
30
|
if (!hasDemo) {
|
package/tools-gh/config.js
CHANGED
|
@@ -19,9 +19,12 @@ const DEFAULTS = {
|
|
|
19
19
|
closeKeyword: 'close',
|
|
20
20
|
},
|
|
21
21
|
release: {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
demo: {
|
|
23
|
+
enable: true,
|
|
24
|
+
dir: 'docs',
|
|
25
|
+
formats: ['gif', 'png'],
|
|
26
|
+
style: 'click',
|
|
27
|
+
},
|
|
25
28
|
},
|
|
26
29
|
changelog: {
|
|
27
30
|
types: {
|
|
@@ -97,6 +100,35 @@ function deepMerge(target, source) {
|
|
|
97
100
|
return result
|
|
98
101
|
}
|
|
99
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Checks for deprecated config keys and prints migration warnings.
|
|
105
|
+
* Old flat release.* keys were moved to release.demo.* in v1.9.0.
|
|
106
|
+
*/
|
|
107
|
+
function warnDeprecatedKeys(userConfig) {
|
|
108
|
+
const deprecated = [
|
|
109
|
+
{ old: 'release.requireDemo', newKey: 'release.demo.enable' },
|
|
110
|
+
{ old: 'release.demoDir', newKey: 'release.demo.dir' },
|
|
111
|
+
{ old: 'release.demoFormats', newKey: 'release.demo.formats' },
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
const found = deprecated.filter(({ old }) => {
|
|
115
|
+
const [top, key] = old.split('.')
|
|
116
|
+
return userConfig[top]?.[key] !== undefined
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
if (found.length === 0) return
|
|
120
|
+
|
|
121
|
+
console.warn('\n⚠️ jst.config.js: устаревшие настройки (обнови конфиг)\n')
|
|
122
|
+
found.forEach(({ old, newKey }) => {
|
|
123
|
+
console.warn(` ${old} → ${newKey}`)
|
|
124
|
+
})
|
|
125
|
+
console.warn('\n Было:')
|
|
126
|
+
console.warn(' release: { requireDemo, demoDir, demoFormats }')
|
|
127
|
+
console.warn('\n Стало (начиная с v1.9.0):')
|
|
128
|
+
console.warn(' release: { demo: { enable, dir, formats, style } }')
|
|
129
|
+
console.warn('\n Запусти: npm run _ upgrade — чтобы получить актуальный конфиг\n')
|
|
130
|
+
}
|
|
131
|
+
|
|
100
132
|
/**
|
|
101
133
|
* Loads config from jst.config.js (preferred) or jst.config.json (fallback)
|
|
102
134
|
* Deep-merges user config with defaults
|
|
@@ -121,5 +153,7 @@ export async function loadConfig() {
|
|
|
121
153
|
}
|
|
122
154
|
}
|
|
123
155
|
|
|
156
|
+
warnDeprecatedKeys(userConfig)
|
|
157
|
+
|
|
124
158
|
return deepMerge(DEFAULTS, userConfig)
|
|
125
159
|
}
|
package/tools-gh/release.js
CHANGED
|
@@ -22,7 +22,7 @@ if (!currentVersion) {
|
|
|
22
22
|
console.log(`📦 Текущая версия: ${currentVersion}`)
|
|
23
23
|
|
|
24
24
|
// 2. Demo check (configurable)
|
|
25
|
-
if (config.release.
|
|
25
|
+
if (config.release.demo.enable) {
|
|
26
26
|
const [major, minor, patch] = currentVersion.split('.').map(Number)
|
|
27
27
|
const recentCommits = execSync('git log --oneline -10', { encoding: 'utf8' })
|
|
28
28
|
const nextVersion = recentCommits.includes('feat:')
|
|
@@ -31,7 +31,7 @@ if (config.release.requireDemo) {
|
|
|
31
31
|
|
|
32
32
|
console.log(`📦 Предполагаемая следующая версия: ${nextVersion}`)
|
|
33
33
|
|
|
34
|
-
const { demoDir, demoFormats } = config.release
|
|
34
|
+
const { dir: demoDir, formats: demoFormats } = config.release.demo
|
|
35
35
|
const hasDemo = demoFormats.some((fmt) => existsSync(`${demoDir}/${nextVersion}.${fmt}`))
|
|
36
36
|
|
|
37
37
|
if (!hasDemo) {
|
|
@@ -42,7 +42,7 @@ if (config.release.requireDemo) {
|
|
|
42
42
|
|
|
43
43
|
console.log(`✅ Демо для ${nextVersion} готово!`)
|
|
44
44
|
} else {
|
|
45
|
-
console.log('⏭️ Проверка демо отключена (release.
|
|
45
|
+
console.log('⏭️ Проверка демо отключена (release.demo.enable = false)')
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
// 3. Warning for 0.x.x
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { execSync
|
|
2
|
+
import { execSync } from 'child_process'
|
|
3
3
|
import { existsSync, readFileSync, writeFileSync } from 'fs'
|
|
4
|
+
import sharp from 'sharp'
|
|
4
5
|
import { loadConfig } from './config.js'
|
|
5
6
|
|
|
6
7
|
const config = await loadConfig()
|
|
7
|
-
const demoDir = config.release.
|
|
8
|
+
const { dir: demoDir, style: demoStyle } = config.release.demo
|
|
8
9
|
|
|
9
10
|
console.log('🎨 Обновляю README релизами с демо...')
|
|
10
11
|
|
|
11
|
-
// Проверяем наличие файлов
|
|
12
12
|
if (!existsSync('CHANGELOG.md')) {
|
|
13
13
|
console.log('❌ CHANGELOG.md не найден')
|
|
14
14
|
console.log('💡 Сначала запусти: npm run _ changelog')
|
|
@@ -23,46 +23,70 @@ if (!existsSync('README.md')) {
|
|
|
23
23
|
const changelog = readFileSync('CHANGELOG.md', 'utf8')
|
|
24
24
|
let readme = readFileSync('README.md', 'utf8')
|
|
25
25
|
|
|
26
|
-
// Получаем
|
|
26
|
+
// Получаем URL репозитория
|
|
27
27
|
let repoUrl
|
|
28
28
|
try {
|
|
29
29
|
const remoteUrl = execSync('git config --get remote.origin.url').toString().trim()
|
|
30
30
|
if (remoteUrl.includes('github.com')) {
|
|
31
31
|
repoUrl = remoteUrl.replace('git@github.com:', 'https://github.com/').replace('.git', '')
|
|
32
32
|
}
|
|
33
|
-
} catch
|
|
33
|
+
} catch {
|
|
34
34
|
console.log('⚠️ Не удалось определить URL репозитория')
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
|
-
*
|
|
39
|
-
* Возвращает true если PNG
|
|
38
|
+
* Извлекает первый кадр GIF в PNG через sharp.
|
|
39
|
+
* Возвращает true если PNG создан успешно.
|
|
40
40
|
*/
|
|
41
|
-
function tryGeneratePng(gifPath, pngPath) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
})
|
|
45
|
-
if (result.status === 0 && existsSync(pngPath)) {
|
|
41
|
+
async function tryGeneratePng(gifPath, pngPath) {
|
|
42
|
+
try {
|
|
43
|
+
await sharp(gifPath).png().toFile(pngPath)
|
|
46
44
|
console.log(`🖼️ PNG превью создан: ${pngPath}`)
|
|
47
45
|
return true
|
|
46
|
+
} catch {
|
|
47
|
+
return false
|
|
48
48
|
}
|
|
49
|
-
return false
|
|
50
49
|
}
|
|
51
50
|
|
|
52
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Генерирует HTML блок демо в зависимости от demoStyle.
|
|
53
|
+
*/
|
|
54
|
+
function renderDemo(version, gifPath, pngPath, hasGif, hasPng) {
|
|
55
|
+
if (demoStyle === 'side-by-side') {
|
|
56
|
+
let html = ''
|
|
57
|
+
if (hasPng) html += `<img src="${pngPath}" alt="${version} demo preview" width="400" />`
|
|
58
|
+
if (hasGif) html += `<img src="${gifPath}" alt="${version} demo animation" width="400" />`
|
|
59
|
+
return `**Демо работы** \n${html}\n\n`
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 'click' (default) — PNG превью, клик открывает GIF
|
|
63
|
+
if (hasGif && hasPng) {
|
|
64
|
+
return (
|
|
65
|
+
`**Демо работы** \n` +
|
|
66
|
+
`<a href="${gifPath}"><img src="${pngPath}" alt="${version} demo preview" width="400" /></a>\n\n` +
|
|
67
|
+
`*${version} — нажми на превью чтобы увидеть анимацию*\n\n`
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
if (hasPng) {
|
|
71
|
+
return `**Демо работы** \n<img src="${pngPath}" alt="${version} demo preview" width="400" />\n\n`
|
|
72
|
+
}
|
|
73
|
+
return `**Демо работы** \n<img src="${gifPath}" alt="${version} demo" width="400" />\n\n`
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Парсим changelog — только версии с демо
|
|
53
77
|
const versionBlocks = changelog.split('## v').slice(1)
|
|
54
78
|
let prettyChangelog = '## 📋 История версий\n\n'
|
|
55
79
|
const processedVersions = new Set()
|
|
56
80
|
|
|
57
|
-
|
|
81
|
+
for (const versionBlock of versionBlocks) {
|
|
58
82
|
// Поддержка заголовков вида "v1.4.0...v2.0.0" (changelogen диапазон) и обычных "v2.0.0"
|
|
59
83
|
const rangeMatch = versionBlock.match(/^\d+\.\d+\.\d+\.\.\.v(\d+\.\d+\.\d+)/)
|
|
60
84
|
const simpleMatch = versionBlock.match(/^(\d+\.\d+\.\d+)/)
|
|
61
85
|
const versionMatch = rangeMatch || simpleMatch
|
|
62
|
-
if (!versionMatch)
|
|
86
|
+
if (!versionMatch) continue
|
|
63
87
|
|
|
64
88
|
const version = `v${versionMatch[1]}`
|
|
65
|
-
if (processedVersions.has(version))
|
|
89
|
+
if (processedVersions.has(version)) continue
|
|
66
90
|
processedVersions.add(version)
|
|
67
91
|
|
|
68
92
|
const gifPath = `${demoDir}/${version}.gif`
|
|
@@ -71,25 +95,25 @@ versionBlocks.forEach((versionBlock) => {
|
|
|
71
95
|
const hasGif = existsSync(gifPath)
|
|
72
96
|
let hasPng = existsSync(pngPath)
|
|
73
97
|
|
|
74
|
-
// Если есть GIF но нет PNG — пытаемся сгенерировать PNG через
|
|
98
|
+
// Если есть GIF но нет PNG — пытаемся сгенерировать PNG через sharp
|
|
75
99
|
if (hasGif && !hasPng) {
|
|
76
|
-
hasPng = tryGeneratePng(gifPath, pngPath)
|
|
100
|
+
hasPng = await tryGeneratePng(gifPath, pngPath)
|
|
77
101
|
}
|
|
78
102
|
|
|
79
103
|
const hasDemo = hasGif || hasPng
|
|
80
104
|
if (!hasDemo) {
|
|
81
105
|
console.log(`⏭️ Пропускаем ${version} - нет демо`)
|
|
82
|
-
|
|
106
|
+
continue
|
|
83
107
|
}
|
|
84
108
|
|
|
85
|
-
// Пропускаем если нет фич
|
|
109
|
+
// Пропускаем если нет фич
|
|
86
110
|
if (
|
|
87
111
|
!versionBlock.includes('### ✨ Новые фичи') &&
|
|
88
112
|
!versionBlock.includes('### ✨ Фичи') &&
|
|
89
113
|
!versionBlock.includes('### 🚀')
|
|
90
114
|
) {
|
|
91
115
|
console.log(`⏭️ Пропускаем ${version} - нет фич`)
|
|
92
|
-
|
|
116
|
+
continue
|
|
93
117
|
}
|
|
94
118
|
|
|
95
119
|
// Извлекаем фичи
|
|
@@ -98,7 +122,6 @@ versionBlocks.forEach((versionBlock) => {
|
|
|
98
122
|
let inFeaturesSection = false
|
|
99
123
|
|
|
100
124
|
for (const line of lines) {
|
|
101
|
-
// Поддержка разных форматов заголовков фич
|
|
102
125
|
if (line.includes('### ✨ Новые фичи') || line.includes('### ✨ Фичи') || line.includes('### 🚀')) {
|
|
103
126
|
inFeaturesSection = true
|
|
104
127
|
continue
|
|
@@ -119,38 +142,24 @@ versionBlocks.forEach((versionBlock) => {
|
|
|
119
142
|
}
|
|
120
143
|
}
|
|
121
144
|
|
|
122
|
-
if (features.length === 0)
|
|
145
|
+
if (features.length === 0) continue
|
|
123
146
|
|
|
124
147
|
console.log(`✅ Добавляем ${version} - есть демо и ${features.length} фич`)
|
|
125
148
|
|
|
126
|
-
// Форматируем версию
|
|
127
149
|
prettyChangelog += `### 🟢 ${version}\n\n`
|
|
150
|
+
prettyChangelog += renderDemo(version, gifPath, pngPath, hasGif, hasPng)
|
|
128
151
|
|
|
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`
|
|
137
|
-
} else {
|
|
138
|
-
prettyChangelog += `**Демо работы** \n<img src="${gifPath}" width="400" />\n\n`
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Добавляем функционал
|
|
142
152
|
prettyChangelog += `**Функционал:**\n`
|
|
143
153
|
features.forEach((feature) => {
|
|
144
154
|
prettyChangelog += `- ${feature}\n`
|
|
145
155
|
})
|
|
146
156
|
|
|
147
|
-
// Добавляем ссылку на релиз (если есть URL репозитория)
|
|
148
157
|
if (repoUrl) {
|
|
149
158
|
prettyChangelog += `\n**Релиз:** ${repoUrl}/releases/tag/${version}\n\n`
|
|
150
159
|
}
|
|
151
160
|
|
|
152
161
|
prettyChangelog += `---\n\n`
|
|
153
|
-
}
|
|
162
|
+
}
|
|
154
163
|
|
|
155
164
|
// Заменяем секцию между маркерами
|
|
156
165
|
if (readme.includes('<!-- AUTOGENERATED_SECTION START -->')) {
|
|
@@ -172,7 +181,6 @@ if (readme.includes('<!-- AUTOGENERATED_SECTION START -->')) {
|
|
|
172
181
|
process.exit(1)
|
|
173
182
|
}
|
|
174
183
|
|
|
175
|
-
// Сохраняем
|
|
176
184
|
writeFileSync('README.md', readme)
|
|
177
185
|
console.log('✅ README обновлён с релизами, у которых есть демо!')
|
|
178
186
|
|
|
@@ -187,6 +195,6 @@ try {
|
|
|
187
195
|
} else {
|
|
188
196
|
console.log('💡 README не изменился')
|
|
189
197
|
}
|
|
190
|
-
} catch
|
|
198
|
+
} catch {
|
|
191
199
|
console.log('💡 README обновлён локально (не удалось запушить автоматически)')
|
|
192
200
|
}
|