@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 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.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",
@@ -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
- spawnSync('git', ['add', 'web/index.html'], { stdio: 'inherit', cwd });
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, '&amp;')
29
+ .replace(/</g, '&lt;')
30
+ .replace(/>/g, '&gt;')
31
+ .replace(/"/g, '&quot;')
32
+ .replace(/'/g, '&#39;');
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: hidden;
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: absolute;
281
- right: -100px;
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: translateY(-50%) translateX(40px);
295
+ transform: translateX(40px);
292
296
  }
293
297
  to {
294
298
  opacity: 1;
295
- transform: translateY(-50%) translateX(0);
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: none;
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.2 - Latest Release</span>
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.2</div>
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.1.9</div>
388
- <div class="release-date">November 24, 2025</div>
389
- <div class="release-type">๐Ÿ› Bug Fixes</div>
390
- <p class="release-description">Convert release scripts to CJS to support ESM projects</p>
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.1.8</div>
394
- <div class="release-date">November 22, 2025</div>
395
- <div class="release-type">๐Ÿ› Bug Fixes</div>
396
- <p class="release-description">Update installation script to copy missing files and update gitignore
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.1.7</div>
401
- <div class="release-date">November 11, 2025</div>
402
- <div class="release-type">๐Ÿ“ Documentation</div>
403
- <p class="release-description">Recommend dev workflow MCP server for enhanced workflow automation
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.1.1</div>
408
- <div class="release-date">November 5, 2025</div>
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">Eliminated fs.F_OK deprecation warning from release runs</p>
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.12</div>
414
- <div class="release-date">October 29, 2025</div>
415
- <div class="release-type">โœจ Features</div>
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
- const codeLines = document.querySelectorAll('.code-line');
151
- let delay = 0;
150
+ function animateTerminal() {
151
+ const codeLines = document.querySelectorAll('.code-line');
152
+ let delay = 0;
152
153
 
153
- codeLines.forEach((line, index) => {
154
- line.style.opacity = '0';
155
- line.style.animation = `fadeIn 0.5s ease-out ${delay}s forwards`;
156
- delay += 0.3;
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');