@vv0rkz/js-template 1.8.2 → 1.8.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vv0rkz/js-template",
3
- "version": "1.8.2",
3
+ "version": "1.8.3",
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"
@@ -83,14 +83,26 @@ export default {
83
83
  * Настройки релиза (`jst release`)
84
84
  */
85
85
  release: {
86
- /** Требовать GIF/PNG демо перед релизом (true | false) */
87
- requireDemo: true,
86
+ /**
87
+ * Настройки демо
88
+ */
89
+ demo: {
90
+ /** Требовать демо перед релизом (true | false) */
91
+ enable: true,
88
92
 
89
- /** Директория для демо-файлов */
90
- demoDir: 'docs',
93
+ /** Директория для демо-файлов */
94
+ dir: 'docs',
91
95
 
92
- /** Допустимые форматы демо-файлов */
93
- demoFormats: ['gif', 'png'],
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.requireDemo) {
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) {
@@ -19,9 +19,12 @@ const DEFAULTS = {
19
19
  closeKeyword: 'close',
20
20
  },
21
21
  release: {
22
- requireDemo: true,
23
- demoDir: 'docs',
24
- demoFormats: ['gif', 'png'],
22
+ demo: {
23
+ enable: true,
24
+ dir: 'docs',
25
+ formats: ['gif', 'png'],
26
+ style: 'click',
27
+ },
25
28
  },
26
29
  changelog: {
27
30
  types: {
@@ -22,7 +22,7 @@ if (!currentVersion) {
22
22
  console.log(`📦 Текущая версия: ${currentVersion}`)
23
23
 
24
24
  // 2. Demo check (configurable)
25
- if (config.release.requireDemo) {
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.requireDemo = false)')
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, spawnSync } from 'child_process'
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.demoDir
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 (error) {
33
+ } catch {
34
34
  console.log('⚠️ Не удалось определить URL репозитория')
35
35
  }
36
36
 
37
37
  /**
38
- * Пытается извлечь первый кадр GIF в PNG через ffmpeg.
39
- * Возвращает true если PNG был создан, false если ffmpeg недоступен или ошибка.
38
+ * Извлекает первый кадр GIF в PNG через sharp.
39
+ * Возвращает true если PNG создан успешно.
40
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)) {
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
- // Парсим changelog - ТОЛЬКО версии с демо
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
- versionBlocks.forEach((versionBlock) => {
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) return
86
+ if (!versionMatch) continue
63
87
 
64
88
  const version = `v${versionMatch[1]}`
65
- if (processedVersions.has(version)) return
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 через ffmpeg
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
- return
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
- return
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) return
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 (error) {
198
+ } catch {
191
199
  console.log('💡 README обновлён локально (не удалось запушить автоматически)')
192
200
  }