@programinglive/commiter 1.2.2 โ 1.2.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/CHANGELOG.md +19 -0
- package/docs/release-notes/RELEASE_NOTES.md +17 -0
- package/package.json +3 -2
- package/scripts/release.cjs +21 -1
- package/scripts/update-web-releases.js +232 -0
- package/web/css/style.css +21 -9
- package/web/index.html +22 -23
- package/web/js/script.js +29 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
### [1.2.4](https://github.com/programinglive/commiter/compare/v1.2.3...v1.2.4) (2025-11-27)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### ๐งน Chores
|
|
9
|
+
|
|
10
|
+
* **release:** 1.2.3 ([9d12f7e](https://github.com/programinglive/commiter/commit/9d12f7e7f9ec5d64b4f7a6d30a4c046e760d73bb))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### ๐ Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **web:** improve terminal layout and animation ([6d196f4](https://github.com/programinglive/commiter/commit/6d196f4f922e72ad32e9cb570046ff7bf82c398e))
|
|
16
|
+
|
|
17
|
+
### [1.2.3](https://github.com/programinglive/commiter/compare/v1.2.2...v1.2.3) (2025-11-27)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### โจ Features
|
|
21
|
+
|
|
22
|
+
* automate website releases timeline updates from release notes ([5abf788](https://github.com/programinglive/commiter/commit/5abf788afd90dde0b8a52dc3658720044f40b2ee))
|
|
23
|
+
|
|
5
24
|
### [1.2.2](https://github.com/programinglive/commiter/compare/v1.2.1...v1.2.2) (2025-11-27)
|
|
6
25
|
|
|
7
26
|
|
|
@@ -4,6 +4,8 @@ This document summarizes every published version of `@programinglive/commiter`.
|
|
|
4
4
|
|
|
5
5
|
| Version | Date | Highlights |
|
|
6
6
|
|---------|------|------------|
|
|
7
|
+
| 1.2.4 | 2025-11-27 | **release:** 1.2.3 (9d12f7e) |
|
|
8
|
+
| 1.2.3 | 2025-11-27 | automate website releases timeline updates from release notes (5abf788) |
|
|
7
9
|
| 1.2.2 | 2025-11-27 | update homepage url (556b173) |
|
|
8
10
|
| 1.2.1 | 2025-11-26 | **release:** improve website version update reliability (18f5ace) |
|
|
9
11
|
| 1.2.0 | 2025-11-26 | See CHANGELOG for details. |
|
|
@@ -46,6 +48,21 @@ This document summarizes every published version of `@programinglive/commiter`.
|
|
|
46
48
|
|
|
47
49
|
|
|
48
50
|
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
## 1.2.4 โ ๐งน Chores
|
|
54
|
+
|
|
55
|
+
Released on **2025-11-27**.
|
|
56
|
+
|
|
57
|
+
- **release:** 1.2.3 (9d12f7e)
|
|
58
|
+
- **web:** improve terminal layout and animation (6d196f4)
|
|
59
|
+
|
|
60
|
+
## 1.2.3 โ โจ Features
|
|
61
|
+
|
|
62
|
+
Released on **2025-11-27**.
|
|
63
|
+
|
|
64
|
+
- automate website releases timeline updates from release notes (5abf788)
|
|
65
|
+
|
|
49
66
|
## 1.2.2 โ ๐งน Chores
|
|
50
67
|
|
|
51
68
|
Released on **2025-11-27**.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@programinglive/commiter",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.4",
|
|
4
4
|
"description": "Commiter keeps repositories release-ready by enforcing conventional commits, generating icon-rich changelog entries, and orchestrating semantic version bumps without manual toil. It bootstraps Husky hooks, commitlint rules, and release scripts that inspect history, detect framework-specific test commands, run them automatically, tag git releases, coordinate npm publishing, surface release metrics, enforce project-specific checks, and give maintainers observability across distributed teams. Plus!",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"release:minor": "node scripts/release.cjs minor",
|
|
15
15
|
"release:patch": "node scripts/release.cjs patch",
|
|
16
16
|
"web": "npx serve web",
|
|
17
|
-
"update:web": "node scripts/update-web-version.js"
|
|
17
|
+
"update:web": "node scripts/update-web-version.js",
|
|
18
|
+
"update:web:releases": "node scripts/update-web-releases.js"
|
|
18
19
|
},
|
|
19
20
|
"keywords": [
|
|
20
21
|
"commit",
|
package/scripts/release.cjs
CHANGED
|
@@ -188,12 +188,15 @@ function runRelease({
|
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
+
let websiteVersionUpdated = false;
|
|
192
|
+
let websiteTimelineUpdated = false;
|
|
193
|
+
|
|
191
194
|
// Update website version
|
|
192
195
|
try {
|
|
193
196
|
console.log('๐ Updating website version...');
|
|
194
197
|
const updateWebResult = spawnSync(process.execPath, ['scripts/update-web-version.js'], { stdio: 'inherit', cwd });
|
|
195
198
|
if (updateWebResult.status === 0) {
|
|
196
|
-
|
|
199
|
+
websiteVersionUpdated = true;
|
|
197
200
|
} else {
|
|
198
201
|
console.warn('โ ๏ธ Failed to update website version');
|
|
199
202
|
}
|
|
@@ -201,6 +204,23 @@ function runRelease({
|
|
|
201
204
|
console.warn(`โ ๏ธ Skipping website version update: ${error.message}`);
|
|
202
205
|
}
|
|
203
206
|
|
|
207
|
+
// Update website releases timeline
|
|
208
|
+
try {
|
|
209
|
+
console.log('๐ Updating website releases timeline...');
|
|
210
|
+
const updateTimelineResult = spawnSync(process.execPath, ['scripts/update-web-releases.js'], { stdio: 'inherit', cwd });
|
|
211
|
+
if (updateTimelineResult.status === 0) {
|
|
212
|
+
websiteTimelineUpdated = true;
|
|
213
|
+
} else {
|
|
214
|
+
console.warn('โ ๏ธ Failed to update website releases timeline');
|
|
215
|
+
}
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.warn(`โ ๏ธ Skipping website releases timeline update: ${error.message}`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (websiteVersionUpdated || websiteTimelineUpdated) {
|
|
221
|
+
spawnSync('git', ['add', 'web/index.html'], { stdio: 'inherit', cwd });
|
|
222
|
+
}
|
|
223
|
+
|
|
204
224
|
return releaseResult;
|
|
205
225
|
}
|
|
206
226
|
if (require.main === module) {
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const DEFAULT_MAX_RELEASES = 5;
|
|
5
|
+
const RELEASE_NOTES_PATH = path.join(__dirname, '..', 'docs', 'release-notes', 'RELEASE_NOTES.md');
|
|
6
|
+
const INDEX_PATH = path.join(__dirname, '..', 'web', 'index.html');
|
|
7
|
+
const MARKER_START = '<!-- RELEASES_TIMELINE:START -->';
|
|
8
|
+
const MARKER_END = '<!-- RELEASES_TIMELINE:END -->';
|
|
9
|
+
|
|
10
|
+
function readFileOrThrow(filePath, label) {
|
|
11
|
+
if (!fs.existsSync(filePath)) {
|
|
12
|
+
throw new Error(`${label} not found at ${filePath}`);
|
|
13
|
+
}
|
|
14
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function sanitizeText(text = '') {
|
|
18
|
+
return text
|
|
19
|
+
.replace(/\*\*/g, '')
|
|
20
|
+
.replace(/`/g, '')
|
|
21
|
+
.replace(/\[([^\]]+)\]\([^\)]+\)/g, '$1')
|
|
22
|
+
.replace(/\s+/g, ' ')
|
|
23
|
+
.trim();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function escapeHtml(text = '') {
|
|
27
|
+
return text
|
|
28
|
+
.replace(/&/g, '&')
|
|
29
|
+
.replace(/</g, '<')
|
|
30
|
+
.replace(/>/g, '>')
|
|
31
|
+
.replace(/"/g, '"')
|
|
32
|
+
.replace(/'/g, ''');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function formatDate(dateString) {
|
|
36
|
+
if (!dateString) {
|
|
37
|
+
return 'Date pending';
|
|
38
|
+
}
|
|
39
|
+
const date = new Date(dateString);
|
|
40
|
+
if (Number.isNaN(date.valueOf())) {
|
|
41
|
+
return dateString;
|
|
42
|
+
}
|
|
43
|
+
return date.toLocaleDateString('en-US', {
|
|
44
|
+
year: 'numeric',
|
|
45
|
+
month: 'long',
|
|
46
|
+
day: 'numeric'
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function extractReleaseSections(content) {
|
|
51
|
+
const normalizedContent = content.replace(/\r\n/g, '\n');
|
|
52
|
+
const tableEntries = extractReleaseTableEntries(normalizedContent);
|
|
53
|
+
const detailMap = extractReleaseDetailsMap(normalizedContent);
|
|
54
|
+
|
|
55
|
+
if (tableEntries.length === 0) {
|
|
56
|
+
return extractSectionEntries(normalizedContent);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return tableEntries.map((entry) => {
|
|
60
|
+
const details = detailMap.get(entry.version) || {};
|
|
61
|
+
return {
|
|
62
|
+
version: entry.version,
|
|
63
|
+
releaseType: details.releaseType || '',
|
|
64
|
+
date: entry.date,
|
|
65
|
+
description: details.description || entry.highlight || 'See CHANGELOG for details.'
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function extractReleaseTableEntries(content) {
|
|
71
|
+
const lines = content.split('\n');
|
|
72
|
+
const headerIndex = lines.findIndex((line) => /^\|\s*Version\s*\|/i.test(line));
|
|
73
|
+
if (headerIndex === -1) {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const entries = [];
|
|
78
|
+
for (let i = headerIndex + 2; i < lines.length; i++) {
|
|
79
|
+
const line = lines[i];
|
|
80
|
+
if (!line.trim().startsWith('|')) {
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
const cells = line.split('|').slice(1, -1).map((cell) => sanitizeText(cell));
|
|
84
|
+
if (cells.length < 3) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const [version, date, highlight] = cells;
|
|
88
|
+
if (!version) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
entries.push({ version, date, highlight });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return entries;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function extractReleaseDetailsMap(content) {
|
|
98
|
+
const map = new Map();
|
|
99
|
+
const sectionRegex = /^##\s+(.+?)\s*$([\s\S]*?)(?=^##\s+|$)/gm;
|
|
100
|
+
let match;
|
|
101
|
+
|
|
102
|
+
while ((match = sectionRegex.exec(content)) !== null) {
|
|
103
|
+
const heading = match[1].trim();
|
|
104
|
+
const body = match[2].trim().replace(/\r/g, '');
|
|
105
|
+
const headingMatch = heading.match(/^(\d+\.\d+\.\d+)(?:\s+[โ-]\s+(.+))?/);
|
|
106
|
+
if (!headingMatch) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const version = headingMatch[1];
|
|
111
|
+
const releaseType = headingMatch[2] ? headingMatch[2].trim() : '';
|
|
112
|
+
const descriptionMatch = body.match(/^\s*\-\s+(.+)$/m);
|
|
113
|
+
const description = descriptionMatch ? sanitizeText(descriptionMatch[1]) : undefined;
|
|
114
|
+
|
|
115
|
+
map.set(version, {
|
|
116
|
+
releaseType,
|
|
117
|
+
description
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return map;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function extractSectionEntries(content) {
|
|
125
|
+
const sections = [];
|
|
126
|
+
const sectionRegex = /^##\s+(.+?)\s*$([\s\S]*?)(?=^##\s+|$)/gm;
|
|
127
|
+
let match;
|
|
128
|
+
|
|
129
|
+
while ((match = sectionRegex.exec(content)) !== null) {
|
|
130
|
+
const heading = match[1].trim();
|
|
131
|
+
const body = match[2].trim().replace(/\r/g, '');
|
|
132
|
+
const headingMatch = heading.match(/^(\d+\.\d+\.\d+)(?:\s+[โ-]\s+(.+))?/);
|
|
133
|
+
if (!headingMatch) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const version = headingMatch[1];
|
|
138
|
+
const releaseType = headingMatch[2] ? headingMatch[2].trim() : '';
|
|
139
|
+
const dateMatch = body.match(/Released on \*\*(\d{4}-\d{2}-\d{2})\*\*\.?/);
|
|
140
|
+
const descriptionMatch = body.match(/^\s*\-\s+(.+)$/m);
|
|
141
|
+
|
|
142
|
+
sections.push({
|
|
143
|
+
version,
|
|
144
|
+
releaseType,
|
|
145
|
+
date: dateMatch ? dateMatch[1] : undefined,
|
|
146
|
+
description: descriptionMatch ? sanitizeText(descriptionMatch[1]) : 'See CHANGELOG for details.'
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return sections;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function renderReleaseItem(release, { latest = false, indent = '' } = {}) {
|
|
154
|
+
const badge = latest ? `\n${indent} <div class="release-badge">Latest</div>` : '';
|
|
155
|
+
const typeLine = release.releaseType
|
|
156
|
+
? `\n${indent} <div class="release-type">${escapeHtml(release.releaseType)}</div>`
|
|
157
|
+
: '';
|
|
158
|
+
|
|
159
|
+
return `${indent}<div class="release-item">${badge}
|
|
160
|
+
${indent} <div class="release-version">v${escapeHtml(release.version)}</div>
|
|
161
|
+
${indent} <div class="release-date">${escapeHtml(formatDate(release.date))}</div>${typeLine}
|
|
162
|
+
${indent} <p class="release-description">${escapeHtml(release.description)}</p>
|
|
163
|
+
${indent}</div>`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function updateIndexHtml({
|
|
167
|
+
htmlContent,
|
|
168
|
+
releases,
|
|
169
|
+
markerStart = MARKER_START,
|
|
170
|
+
markerEnd = MARKER_END,
|
|
171
|
+
indent = ' '
|
|
172
|
+
}) {
|
|
173
|
+
const startIndex = htmlContent.indexOf(markerStart);
|
|
174
|
+
const endIndex = htmlContent.indexOf(markerEnd);
|
|
175
|
+
|
|
176
|
+
if (startIndex === -1 || endIndex === -1 || endIndex <= startIndex) {
|
|
177
|
+
throw new Error('Release timeline markers not found in web/index.html');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const before = htmlContent.slice(0, startIndex + markerStart.length);
|
|
181
|
+
const after = htmlContent.slice(endIndex);
|
|
182
|
+
const timeline = releases
|
|
183
|
+
.map((release, idx) => renderReleaseItem(release, { latest: idx === 0, indent }))
|
|
184
|
+
.join('\n');
|
|
185
|
+
|
|
186
|
+
return `${before}\n${timeline}\n${indent}${after.trimStart()}`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function updateWebReleases({
|
|
190
|
+
releaseNotesPath = RELEASE_NOTES_PATH,
|
|
191
|
+
indexPath = INDEX_PATH,
|
|
192
|
+
maxReleases = DEFAULT_MAX_RELEASES
|
|
193
|
+
} = {}) {
|
|
194
|
+
const notesContent = readFileOrThrow(releaseNotesPath, 'Release notes');
|
|
195
|
+
const releases = extractReleaseSections(notesContent).slice(0, maxReleases);
|
|
196
|
+
|
|
197
|
+
if (releases.length === 0) {
|
|
198
|
+
console.warn('โ ๏ธ No release sections found; skipping website update.');
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const htmlContent = readFileOrThrow(indexPath, 'Landing page');
|
|
203
|
+
const nextContent = updateIndexHtml({ htmlContent, releases });
|
|
204
|
+
fs.writeFileSync(indexPath, nextContent);
|
|
205
|
+
console.log(`โ
Updated website releases timeline with ${releases.length} entries.`);
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function main() {
|
|
210
|
+
try {
|
|
211
|
+
updateWebReleases();
|
|
212
|
+
} catch (error) {
|
|
213
|
+
console.error(`โ Failed to update website releases timeline: ${error.message}`);
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
module.exports = {
|
|
219
|
+
escapeHtml,
|
|
220
|
+
sanitizeText,
|
|
221
|
+
formatDate,
|
|
222
|
+
extractReleaseSections,
|
|
223
|
+
renderReleaseItem,
|
|
224
|
+
updateIndexHtml,
|
|
225
|
+
updateWebReleases,
|
|
226
|
+
MARKER_START,
|
|
227
|
+
MARKER_END
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
if (require.main === module) {
|
|
231
|
+
main();
|
|
232
|
+
}
|
package/web/css/style.css
CHANGED
|
@@ -163,7 +163,14 @@ body {
|
|
|
163
163
|
.hero {
|
|
164
164
|
padding: calc(var(--spacing-3xl) + 60px) 0 var(--spacing-3xl);
|
|
165
165
|
position: relative;
|
|
166
|
-
overflow:
|
|
166
|
+
overflow: visible;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.hero .container {
|
|
170
|
+
display: grid;
|
|
171
|
+
grid-template-columns: 1fr 1fr;
|
|
172
|
+
gap: var(--spacing-3xl);
|
|
173
|
+
align-items: center;
|
|
167
174
|
}
|
|
168
175
|
|
|
169
176
|
.hero::before {
|
|
@@ -277,22 +284,19 @@ body {
|
|
|
277
284
|
|
|
278
285
|
/* Hero Visual */
|
|
279
286
|
.hero-visual {
|
|
280
|
-
position:
|
|
281
|
-
|
|
282
|
-
top: 50%;
|
|
283
|
-
transform: translateY(-50%);
|
|
284
|
-
width: 600px;
|
|
287
|
+
position: relative;
|
|
288
|
+
width: 100%;
|
|
285
289
|
animation: fadeInRight 0.8s ease-out 0.5s both;
|
|
286
290
|
}
|
|
287
291
|
|
|
288
292
|
@keyframes fadeInRight {
|
|
289
293
|
from {
|
|
290
294
|
opacity: 0;
|
|
291
|
-
transform:
|
|
295
|
+
transform: translateX(40px);
|
|
292
296
|
}
|
|
293
297
|
to {
|
|
294
298
|
opacity: 1;
|
|
295
|
-
transform:
|
|
299
|
+
transform: translateX(0);
|
|
296
300
|
}
|
|
297
301
|
}
|
|
298
302
|
|
|
@@ -338,6 +342,9 @@ body {
|
|
|
338
342
|
padding: var(--spacing-md);
|
|
339
343
|
font-family: 'Courier New', monospace;
|
|
340
344
|
font-size: 0.875rem;
|
|
345
|
+
max-height: none;
|
|
346
|
+
overflow-y: auto;
|
|
347
|
+
min-height: auto;
|
|
341
348
|
}
|
|
342
349
|
|
|
343
350
|
.code-line {
|
|
@@ -870,8 +877,13 @@ body {
|
|
|
870
877
|
Responsive Design
|
|
871
878
|
=========================== */
|
|
872
879
|
@media (max-width: 1024px) {
|
|
880
|
+
.hero .container {
|
|
881
|
+
grid-template-columns: 1fr;
|
|
882
|
+
}
|
|
883
|
+
|
|
873
884
|
.hero-visual {
|
|
874
|
-
display:
|
|
885
|
+
display: block;
|
|
886
|
+
margin-top: var(--spacing-2xl);
|
|
875
887
|
}
|
|
876
888
|
|
|
877
889
|
.hero-content {
|
package/web/index.html
CHANGED
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
<div class="hero-content">
|
|
67
67
|
<div class="hero-badge">
|
|
68
68
|
<span class="badge-dot"></span>
|
|
69
|
-
<span>v1.2.
|
|
69
|
+
<span>v1.2.4 - Latest Release</span>
|
|
70
70
|
</div>
|
|
71
71
|
<h1 class="hero-title">
|
|
72
72
|
Ship Releases with <span class="gradient-text">Confidence</span>
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
</div>
|
|
91
91
|
<div class="hero-stats">
|
|
92
92
|
<div class="stat-item">
|
|
93
|
-
<div class="stat-value">1.2.
|
|
93
|
+
<div class="stat-value">1.2.4</div>
|
|
94
94
|
<div class="stat-label">Latest Version</div>
|
|
95
95
|
</div>
|
|
96
96
|
<div class="stat-item">
|
|
@@ -382,39 +382,38 @@
|
|
|
382
382
|
<p class="section-subtitle">Latest updates and improvements</p>
|
|
383
383
|
</div>
|
|
384
384
|
<div class="releases-timeline">
|
|
385
|
+
<!-- RELEASES_TIMELINE:START -->
|
|
385
386
|
<div class="release-item">
|
|
386
387
|
<div class="release-badge">Latest</div>
|
|
387
|
-
<div class="release-version">v1.
|
|
388
|
-
<div class="release-date">November
|
|
389
|
-
<div class="release-type"
|
|
390
|
-
<p class="release-description">
|
|
388
|
+
<div class="release-version">v1.2.4</div>
|
|
389
|
+
<div class="release-date">November 27, 2025</div>
|
|
390
|
+
<div class="release-type">๐งน Chores</div>
|
|
391
|
+
<p class="release-description">release: 1.2.3 (9d12f7e)</p>
|
|
391
392
|
</div>
|
|
392
393
|
<div class="release-item">
|
|
393
|
-
<div class="release-version">v1.
|
|
394
|
-
<div class="release-date">November
|
|
395
|
-
<div class="release-type"
|
|
396
|
-
<p class="release-description">
|
|
397
|
-
</p>
|
|
394
|
+
<div class="release-version">v1.2.3</div>
|
|
395
|
+
<div class="release-date">November 27, 2025</div>
|
|
396
|
+
<div class="release-type">โจ Features</div>
|
|
397
|
+
<p class="release-description">automate website releases timeline updates from release notes (5abf788)</p>
|
|
398
398
|
</div>
|
|
399
399
|
<div class="release-item">
|
|
400
|
-
<div class="release-version">v1.
|
|
401
|
-
<div class="release-date">November
|
|
402
|
-
<div class="release-type"
|
|
403
|
-
<p class="release-description">
|
|
404
|
-
</p>
|
|
400
|
+
<div class="release-version">v1.2.2</div>
|
|
401
|
+
<div class="release-date">November 27, 2025</div>
|
|
402
|
+
<div class="release-type">๐งน Chores</div>
|
|
403
|
+
<p class="release-description">update homepage url (556b173)</p>
|
|
405
404
|
</div>
|
|
406
405
|
<div class="release-item">
|
|
407
|
-
<div class="release-version">v1.
|
|
408
|
-
<div class="release-date">November
|
|
406
|
+
<div class="release-version">v1.2.1</div>
|
|
407
|
+
<div class="release-date">November 26, 2025</div>
|
|
409
408
|
<div class="release-type">๐ Bug Fixes</div>
|
|
410
|
-
<p class="release-description">
|
|
409
|
+
<p class="release-description">release: improve website version update reliability (18f5ace)</p>
|
|
411
410
|
</div>
|
|
412
411
|
<div class="release-item">
|
|
413
|
-
<div class="release-version">v1.0
|
|
414
|
-
<div class="release-date">
|
|
415
|
-
<
|
|
416
|
-
<p class="release-description">Autodetects project test command before releasing</p>
|
|
412
|
+
<div class="release-version">v1.2.0</div>
|
|
413
|
+
<div class="release-date">November 26, 2025</div>
|
|
414
|
+
<p class="release-description">See CHANGELOG for details.</p>
|
|
417
415
|
</div>
|
|
416
|
+
<!-- RELEASES_TIMELINE:END -->
|
|
418
417
|
</div>
|
|
419
418
|
<div class="releases-cta">
|
|
420
419
|
<a href="https://github.com/programinglive/commiter/releases" target="_blank" class="btn btn-secondary">
|
package/web/js/script.js
CHANGED
|
@@ -147,14 +147,36 @@ window.addEventListener('scroll', () => {
|
|
|
147
147
|
// ===========================
|
|
148
148
|
// Terminal Animation
|
|
149
149
|
// ===========================
|
|
150
|
-
|
|
151
|
-
|
|
150
|
+
function animateTerminal() {
|
|
151
|
+
const codeLines = document.querySelectorAll('.code-line');
|
|
152
|
+
let delay = 0;
|
|
152
153
|
|
|
153
|
-
codeLines.forEach((line, index) => {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
154
|
+
codeLines.forEach((line, index) => {
|
|
155
|
+
line.style.opacity = '0';
|
|
156
|
+
line.style.animation = 'none';
|
|
157
|
+
// Trigger reflow to restart animation
|
|
158
|
+
void line.offsetWidth;
|
|
159
|
+
line.style.animation = `fadeIn 0.5s ease-out ${delay}s forwards`;
|
|
160
|
+
delay += 0.3;
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Initial animation on page load
|
|
165
|
+
animateTerminal();
|
|
166
|
+
|
|
167
|
+
// Replay animation when scrolling back to the terminal
|
|
168
|
+
const codeWindow = document.querySelector('.code-window');
|
|
169
|
+
if (codeWindow) {
|
|
170
|
+
const terminalObserver = new IntersectionObserver((entries) => {
|
|
171
|
+
entries.forEach(entry => {
|
|
172
|
+
if (entry.isIntersecting) {
|
|
173
|
+
animateTerminal();
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}, { threshold: 0.5 });
|
|
177
|
+
|
|
178
|
+
terminalObserver.observe(codeWindow);
|
|
179
|
+
}
|
|
158
180
|
|
|
159
181
|
// Add fadeIn keyframe animation
|
|
160
182
|
const style = document.createElement('style');
|