diff --git a/docs/changelog/assets/css/app.css b/docs/changelog/assets/css/app.css
index 31de425f3..a6607e7d0 100644
--- a/docs/changelog/assets/css/app.css
+++ b/docs/changelog/assets/css/app.css
@@ -1,9 +1,9 @@
-/*
+/*
* CSS Variables for consistent theming
- *
+ *
* Usage in CSS: background-color: var(--color-webex-blue);
* Usage in JS: element.style.backgroundColor = 'var(--color-success)';
- *
+ *
* Available variables:
* --color-success: Success state (green)
* --color-success-hover: Success hover state
diff --git a/docs/changelog/assets/js/app.js b/docs/changelog/assets/js/app.js
index 6368c66d1..fa8439b31 100644
--- a/docs/changelog/assets/js/app.js
+++ b/docs/changelog/assets/js/app.js
@@ -5,17 +5,9 @@ let comparisonListenersInitialized = false;
const githubBaseUrl = 'https://github.com/webex/widgets/';
import {
comparisonState,
- extractPackagesFromVersion,
- findLatestPackageVersion,
- getEffectiveVersion,
- getPackageVersion,
- determinePackageStatus,
- createPackageComparisonRow,
- calculateComparisonStats,
- buildPackagesList,
- comparePackages,
- fetchAndCompareVersions,
generatePackageComparisonData,
+ sortStableVersions,
+ collectCommitsFromStable,
} from './comparison-view.js';
// DOM elements
@@ -133,17 +125,18 @@ Handlebars.registerHelper('convertDate', function (timestamp) {
return `${new Date(timestamp).toDateString()} ${new Date(timestamp).toTimeString()}`;
});
+Handlebars.registerHelper('math', function (index, offset) {
+ return index + offset;
+});
+
// Util Methods
const populateFormFieldsFromURL = async () => {
const queryParams = new URLSearchParams(window.location.search);
- // Skip single-view URL handling if comparison parameters are present
if (
- queryParams.has('compare') ||
- queryParams.has('compareStableA') ||
- (queryParams.has('versionA') && queryParams.has('versionB'))
- ) {
- return; // Comparison mode will handle these parameters
+ queryParams.has('compareStableA'))
+ {
+ return;
}
const searchParams = {
@@ -680,20 +673,6 @@ window.onhashchange = () => {
populateVersions();
-let comparisonMode = false;
-/* ============================================
- UI HELPER FUNCTIONS
- ============================================ */
-
-/**
- * Show loading state for comparison
- */
-const showComparisonLoading = () => {
- if (!comparisonResults) return;
- comparisonResults.innerHTML = '
Loading comparison...
';
- comparisonResults.classList.remove('hide');
-};
-
/**
* Show error state for comparison
* @param {Error} error - The error object
@@ -714,145 +693,6 @@ const showComparisonError = (error) => {
DATA LAYER FUNCTIONS
============================================ */
-/**
- * UI LAYER: Handle version comparison UI updates
- * @param {string} versionA - Base version
- * @param {string} versionB - Target version
- */
-const performVersionComparison = async (versionA, versionB) => {
- // Show loading state
- showComparisonLoading();
-
- try {
- // Fetch and compare data (pure data logic)
- const result = await fetchAndCompareVersions(versionA, versionB, versionPaths);
-
- // Display results (UI logic)
- displayComparison(result.versionA, result.versionB, result.comparisonData);
- } catch (error) {
- // Handle error display (UI logic)
- showComparisonError(error);
- }
-};
-
-/**
- * Display comparison results
- * @param {string} versionA - Base version
- * @param {string} versionB - Target version
- * @param {Object} comparisonData - Comparison results
- */
-const displayComparison = (versionA, versionB, comparisonData) => {
- if (!comparisonResults) {
- console.error('comparison-results element not found!');
- return;
- }
-
- if (!comparisonTemplateElement) {
- console.error('comparison-template element not found!');
- return;
- }
-
- const comparisonTemplate = Handlebars.compile(comparisonTemplateElement.innerHTML);
-
- const templateData = {
- versionA,
- versionB,
- ...comparisonData,
- };
-
- console.log('Template data:', templateData);
-
- try {
- const html = comparisonTemplate(templateData);
- console.log('Generated HTML length:', html.length);
-
- comparisonResults.innerHTML = html;
- comparisonResults.classList.remove('hide');
-
- // Update URL with comparison parameters for permalinks
- updateComparisonURL(versionA, versionB);
-
- // Show the copy link button and helper text
- if (copyComparisonLinkBtn) {
- copyComparisonLinkBtn.classList.remove('hide');
- console.log('Copy link button shown');
- } else {
- console.warn('Copy link button not found in DOM');
- }
- if (comparisonHelper) {
- comparisonHelper.classList.remove('hide');
- }
-
- // Scroll to results smoothly
- setTimeout(() => {
- comparisonResults.scrollIntoView({behavior: 'smooth', block: 'start'});
- }, 100);
-
- console.log('Comparison displayed successfully');
- } catch (error) {
- console.error('Error rendering template:', error);
- comparisonResults.innerHTML = `Error rendering comparison: ${error.message}
`;
- }
-};
-
-/**
- * Update URL with comparison parameters for sharing/bookmarking
- * @param {string} versionA - Base version
- * @param {string} versionB - Target version
- */
-const updateComparisonURL = (versionA, versionB) => {
- const url = new URL(window.location);
-
- // Clear any single-view parameters
- url.searchParams.delete('stable_version');
- url.searchParams.delete('package');
- url.searchParams.delete('version');
- url.searchParams.delete('commitMessage');
- url.searchParams.delete('commitHash');
- // Clear enhanced (package-level) comparison params so full comparison link is not stale
- url.searchParams.delete('compareStableA');
- url.searchParams.delete('compareStableB');
- url.searchParams.delete('comparePackage');
- url.searchParams.delete('compareVersionA');
- url.searchParams.delete('compareVersionB');
-
-
- // Set comparison parameters
- url.searchParams.set('compare', `${versionA}vs${versionB}`);
-
- // Update URL without reloading the page
- window.history.pushState({}, '', url);
-};
-
-/**
- * Parse and handle comparison URL parameters
- * Supports formats: ?compare=3.9.0vs3.10.0 or ?versionA=3.9.0&versionB=3.10.0
- */
-const handleComparisonURLParams = async () => {
- const urlParams = new URLSearchParams(window.location.search);
-
- let versionA = null;
- let versionB = null;
-
- // Check for ?compare=AvB format
- const compareParam = urlParams.get('compare');
- if (compareParam && compareParam.includes('vs')) {
- const versions = compareParam.split('vs');
- versionA = versions[0]?.trim();
- versionB = versions[1]?.trim();
- }
-
- // Also support ?versionA=X&versionB=Y format
- if (!versionA) versionA = urlParams.get('versionA');
- if (!versionB) versionB = urlParams.get('versionB');
-
- // If comparison parameters are found, switch to comparison mode
- if (versionA && versionB && versionA !== versionB) {
- return {versionA, versionB, shouldCompare: true};
- }
-
- return {shouldCompare: false};
-};
/**
* Switch to comparison mode programmatically
@@ -860,9 +700,6 @@ const handleComparisonURLParams = async () => {
* @param {string} versionB - Target version (optional)
*/
const switchToComparisonMode = (versionA = null, versionB = null) => {
- // Update mode
- comparisonMode = true;
-
// Update button states
if (comparisonViewBtn && singleViewBtn) {
comparisonViewBtn.classList.add('active', 'btn-primary');
@@ -920,9 +757,8 @@ const getUnionPackages = (changelogA, changelogB) => {
* @param {Object} changelogA - Changelog A
* @param {Object} changelogB - Changelog B
*/
-const compareAndRenderPackageVersions = (packageName, versionASpecific, versionBSpecific, changelogA, changelogB) => {
+const compareAndRenderPackageVersions = (packageName, versionASpecific, versionBSpecific, changelogA, changelogB, totalCommits = 0) => {
try {
- // Generate comparison data (pure data logic from comparison-view.js)
const comparisonData = generatePackageComparisonData(
packageName,
versionASpecific,
@@ -931,9 +767,8 @@ const compareAndRenderPackageVersions = (packageName, versionASpecific, versionB
changelogB
);
- console.log('comparisonData', comparisonData);
+ comparisonData.totalCommits = totalCommits;
- // Validate DOM elements
if (!comparisonResults) {
console.error('comparison-results element not found');
return;
@@ -944,12 +779,12 @@ const compareAndRenderPackageVersions = (packageName, versionASpecific, versionB
return;
}
- // Render template
const template = Handlebars.compile(comparisonTemplateElement.innerHTML);
const html = template(comparisonData);
- // Update DOM
- comparisonResults.innerHTML = html;
+ // NOTE: renderCommitHistory must be called first — it resets this container via `=`.
+ // This `+=` appends the package comparison table below the commit history.
+ comparisonResults.innerHTML += html;
comparisonResults.classList.remove('hide');
// Update URL for sharing
@@ -996,7 +831,7 @@ const populateUnionPackages = (changelogA, changelogB) => {
return;
}
- let optionsHtml = '';
+ let optionsHtml = '';
allPackages.forEach((pkg) => {
optionsHtml += ``;
});
@@ -1117,18 +952,6 @@ const handleEnhancedComparisonURL = async () => {
return {shouldCompare: false};
};
-
-/**
- * Populate version dropdowns for comparison mode
- */
-const populateComparisonVersions = () => {
- if (versionSelectDropdown && versionSelectDropdown.innerHTML) {
- const options = versionSelectDropdown.innerHTML;
- if (versionASelect) versionASelect.innerHTML = options;
- if (versionBSelect) versionBSelect.innerHTML = options;
- }
-};
-
/**
* Reset comparison form selections
*/
@@ -1162,9 +985,6 @@ const clearComparisonForm = () => {
const clearComparisonURLParams = () => {
const url = new URL(window.location);
[
- 'compare',
- 'versionA',
- 'versionB',
'compareStableA',
'compareStableB',
'comparePackage',
@@ -1195,8 +1015,7 @@ const updateCompareButtonState = () => {
compareButton.disabled = false;
}
} else {
- // No package selected - enable for full version comparison
- compareButton.disabled = false;
+ compareButton.disabled = true;
}
};
@@ -1216,7 +1035,6 @@ const updatePrereleaseLabels = () => {
* Handle stable version changes - fetch changelogs and populate packages
*/
const handleStableVersionChange = async () => {
- console.log('🟢 handleStableVersionChange FIRED');
const stableA = versionASelect.value;
const stableB = versionBSelect.value;
@@ -1244,7 +1062,6 @@ const handleStableVersionChange = async () => {
* Handle package selection - populate pre-release versions
*/
const handlePackageChange = () => {
- console.log('🟢 handlePackageChange FIRED');
const selectedPackage = comparisonPackageSelect.value;
if (versionAPrereleaseSelect) versionAPrereleaseSelect.value = '';
@@ -1279,8 +1096,6 @@ const handlePackageChange = () => {
* Switch to single view mode
*/
const switchToSingleViewMode = () => {
- console.log('🔵 Switching to SINGLE VIEW mode');
- comparisonMode = false;
// Update button styles
singleViewBtn.classList.add('active', 'btn-primary');
@@ -1294,25 +1109,6 @@ const switchToSingleViewMode = () => {
clearComparisonURLParams();
};
-/**
- * Switch to comparison view mode
- */
-const switchToComparisonViewMode = () => {
- console.log('🔵 Switching to COMPARISON VIEW mode');
- comparisonMode = true;
-
- // Update button styles
- comparisonViewBtn.classList.add('active', 'btn-primary');
- comparisonViewBtn.classList.remove('btn-default');
- singleViewBtn.classList.remove('active', 'btn-primary');
- singleViewBtn.classList.add('btn-default');
-
- // Toggle visibility (centralized view state)
- updateUIVisibility('comparison');
-
- populateComparisonVersions();
-};
-
/**
* Validate comparison form inputs
*/
@@ -1327,13 +1123,132 @@ const validateComparisonInputs = (stableA, stableB, selectedPackage, versionASpe
return false;
}
+ // Base stable version must be older than or equal to target
+ const allStables = sortStableVersions(Object.keys(versionPaths));
+ const idxA = allStables.indexOf(stableA);
+ const idxB = allStables.indexOf(stableB);
+
+ if (idxA > idxB) {
+ alert('Base version must be older than or equal to target version');
+ return false;
+ }
+
+ // If same stable and both pre-releases selected, base must be older
+ if (stableA === stableB && versionASpecific && versionBSpecific) {
+ const numA = parseInt(versionASpecific.match(/\.(\d+)$/)?.[1] || '0', 10);
+ const numB = parseInt(versionBSpecific.match(/\.(\d+)$/)?.[1] || '0', 10);
+ if (numA > numB) {
+ alert('Base pre-release version must be older than target pre-release version');
+ return false;
+ }
+ }
+
return true;
};
+/* ============================================
+ CROSS-STABLE COMMIT AGGREGATION (app-level)
+ These functions interact with versionPaths and
+ the DOM, so they live here instead of comparison-view.js
+ ============================================ */
+
+/**
+ * Return the ordered list of stable versions between stableA and stableB (inclusive).
+ */
+const getStableVersionsBetween = (stableA, stableB) => {
+ const allStables = sortStableVersions(Object.keys(versionPaths));
+ const idxA = allStables.indexOf(stableA);
+ const idxB = allStables.indexOf(stableB);
+ if (idxA === -1 || idxB === -1) return [];
+ const lo = Math.min(idxA, idxB);
+ const hi = Math.max(idxA, idxB);
+ return allStables.slice(lo, hi + 1);
+};
+
+/**
+ * Walk every stable between stableA..stableB,
+ * fetch each log file, and collect deduplicated commits.
+ */
+const collectCommitsAcrossStables = async (packageName, stableA, stableB, versionA, versionB) => {
+ const stables = getStableVersionsBetween(stableA, stableB);
+ if (stables.length === 0) return [];
+
+ const allCommits = [];
+ for (let i = 0; i < stables.length; i++) {
+ const stable = stables[i];
+ const path = versionPaths[stable];
+ if (!path) continue;
+
+ let changelog;
+ if (stable === stableA && comparisonState.cachedChangelogA ) {
+ changelog = comparisonState.cachedChangelogA;
+ } else if (stable === stableB && comparisonState.cachedChangelogB) {
+ changelog = comparisonState.cachedChangelogB;
+ } else {
+ try {
+ const res = await fetch(path);
+ if (!res.ok) {
+ throw new Error(`Failed to fetch changelog for stable ${stable}`);
+ }
+ changelog = await res.json();
+ } catch (error) {
+
+ continue;
+ }
+ }
+
+ const packageData = changelog[packageName];
+ if (!packageData) continue;
+
+ let position;
+ if (stables.length === 1) position = 'only';
+ else if (i === 0) position = 'start';
+ else if (i === stables.length - 1) position = 'end';
+ else position = 'middle';
+
+ const commits = collectCommitsFromStable(packageData, stable, versionA, versionB, position);
+ allCommits.push(...commits);
+ }
+
+ // Final dedup by hash
+ const seen = new Map();
+ allCommits.forEach((c) => {
+ if (!seen.has(c.hash)) seen.set(c.hash, c);
+ });
+ return Array.from(seen.values());
+};
+
+/**
+ * Render the commit history using the Handlebars template in index.html
+ */
+const renderCommitHistory = (packageName, versionA, versionB, commits) => {
+ const templateEl = document.getElementById('commit-history-template');
+ if (!templateEl) {
+ console.error('commit-history-template not found in DOM');
+ return;
+ }
+
+ const template = Handlebars.compile(templateEl.innerHTML);
+ const html = template({
+ packageName,
+ versionA,
+ versionB,
+ commits,
+ totalCommits: commits.length,
+ githubBaseUrl,
+ });
+
+ comparisonResults.innerHTML = html;
+ comparisonResults.classList.remove('hide');
+
+ if (copyComparisonLinkBtn) copyComparisonLinkBtn.classList.remove('hide');
+ if (comparisonHelper) comparisonHelper.classList.remove('hide');
+};
+
/**
* Handle comparison form submission
*/
-const handleComparisonSubmit = (event) => {
+const handleComparisonSubmit = async (event) => {
event.preventDefault();
const stableA = versionASelect.value;
@@ -1347,21 +1262,38 @@ const handleComparisonSubmit = (event) => {
}
if (selectedPackage && (versionASpecific || versionBSpecific)) {
- // Package-level comparison
const finalVersionA = versionASpecific || stableA;
const finalVersionB = versionBSpecific || stableB;
- console.log('Comparing:', finalVersionA, 'vs', finalVersionB);
- compareAndRenderPackageVersions(
- selectedPackage,
- finalVersionA,
- finalVersionB,
- comparisonState.cachedChangelogA,
- comparisonState.cachedChangelogB
- );
+ try {
+ const commits = await collectCommitsAcrossStables(
+ selectedPackage,
+ stableA,
+ stableB,
+ finalVersionA,
+ finalVersionB
+ );
+
+ // Order matters: renderCommitHistory resets the container; compareAndRender appends to it.
+ renderCommitHistory(selectedPackage, finalVersionA, finalVersionB, commits);
+
+ // Package-level comparison table (appended below)
+ compareAndRenderPackageVersions(
+ selectedPackage,
+ finalVersionA,
+ finalVersionB,
+ comparisonState.cachedChangelogA,
+ comparisonState.cachedChangelogB,
+ commits.length
+ );
+
+ updateEnhancedComparisonURL(stableA, stableB, selectedPackage, finalVersionA, finalVersionB);
+ } catch (error) {
+ showComparisonError(error);
+ }
} else {
- // Full version comparison
- performVersionComparison(stableA, stableB);
+ alert('Please select a package and at least one pre-release version to compare.');
+ return;
}
if (compareButton) compareButton.disabled = false;
@@ -1380,15 +1312,13 @@ const handleClearClick = () => {
*/
const setupComparisonEventListeners = () => {
if (comparisonListenersInitialized) {
- console.log('🔴 Comparison listeners already initialized,skipping......');
return;
}
- console.log('🟢 Setting up comparison event listeners first time......');
comparisonListenersInitialized = true;
// Mode toggle buttons
if (singleViewBtn) singleViewBtn.addEventListener('click', switchToSingleViewMode);
- if (comparisonViewBtn) comparisonViewBtn.addEventListener('click', switchToComparisonViewMode);
+ if (comparisonViewBtn) comparisonViewBtn.addEventListener('click', () => switchToComparisonMode());
// Version and package selectors
if (versionASelect) versionASelect.addEventListener('change', handleStableVersionChange);
@@ -1432,24 +1362,33 @@ const loadEnhancedComparisonFromURL = async (enhancedParams) => {
versionAPrereleaseSelect.value = enhancedParams.versionA;
versionBPrereleaseSelect.value = enhancedParams.versionB;
- compareAndRenderPackageVersions(
- enhancedParams.packageName,
- enhancedParams.versionA,
- enhancedParams.versionB,
- comparisonState.cachedChangelogA,
- comparisonState.cachedChangelogB
- );
-};
-
-/**
- * Handle standard comparison URL parameters on page load
- */
-const loadStandardComparisonFromURL = async (urlParams) => {
- switchToComparisonMode(urlParams.versionA, urlParams.versionB);
+ try {
+ const commits = await collectCommitsAcrossStables(
+ enhancedParams.packageName,
+ enhancedParams.stableA,
+ enhancedParams.stableB,
+ enhancedParams.versionA,
+ enhancedParams.versionB
+ );
- await new Promise((resolve) => setTimeout(resolve, 300));
+ renderCommitHistory(
+ enhancedParams.packageName,
+ enhancedParams.versionA,
+ enhancedParams.versionB,
+ commits
+ );
- performVersionComparison(urlParams.versionA, urlParams.versionB);
+ compareAndRenderPackageVersions(
+ enhancedParams.packageName,
+ enhancedParams.versionA,
+ enhancedParams.versionB,
+ comparisonState.cachedChangelogA,
+ comparisonState.cachedChangelogB,
+ commits.length
+ );
+ } catch (error) {
+ console.error('Error loading commit history from URL:', error);
+ }
};
/**
@@ -1465,12 +1404,6 @@ const initializeComparisonMode = async () => {
await loadEnhancedComparisonFromURL(enhancedParams);
return;
}
-
- // Check for standard comparison URL
- const urlParams = await handleComparisonURLParams();
- if (urlParams.shouldCompare) {
- await loadStandardComparisonFromURL(urlParams);
- }
};
/**
diff --git a/docs/changelog/assets/js/comparison-view.js b/docs/changelog/assets/js/comparison-view.js
index 7352606aa..80e5363f4 100644
--- a/docs/changelog/assets/js/comparison-view.js
+++ b/docs/changelog/assets/js/comparison-view.js
@@ -19,53 +19,6 @@ const comparisonState = {
this.currentStableB = stableB;
},
};
-const extractPackagesFromVersion = (changelog, specificVersions = null) => {
- const packageMap = {};
-
- for (const packageName of Object.keys(changelog)) {
- const packageVersions = changelog[packageName];
- console.log('packageVersions', packageVersions);
-
- // Safety check: ensure packageVersions is an object
- if (!packageVersions || typeof packageVersions !== 'object') continue;
-
- const versionKeys = Object.keys(packageVersions);
- console.log('versionKeys', versionKeys);
-
- if (versionKeys.length === 0) continue;
-
- let selectedVersion = null;
-
- // Check if user specified a specific version for this package
- if (specificVersions && specificVersions[packageName]) {
- const requestedVersion = specificVersions[packageName];
-
- if (packageVersions[requestedVersion]) {
- selectedVersion = requestedVersion;
- }
- }
-
- // If no specific version requested or not found, use earliest (first) version
- if (!selectedVersion) {
- let earliestVersion = versionKeys[0];
- let earliestDate = packageVersions[earliestVersion]?.published_date || Infinity;
-
- for (const version of versionKeys) {
- const publishedDate = packageVersions[version]?.published_date || Infinity;
- if (publishedDate < earliestDate) {
- earliestDate = publishedDate;
- earliestVersion = version;
- }
- }
-
- selectedVersion = earliestVersion;
- }
-
- packageMap[packageName] = selectedVersion;
- }
-
- return packageMap;
-};
const findLatestPackageVersion = (changelog, packageName) => {
if (!changelog[packageName]) return null;
@@ -202,145 +155,148 @@ const buildPackagesList = (
return packagesArray;
};
-const comparePackages = (packagesA, packagesB, changelogA, changelogB, stableVersionA, stableVersionB) => {
- // Get ALL package names from both changelogs (entire changelog, not just specific versions)
- const allPackageNames = new Set([
- ...Object.keys(changelogA), //ALL packages in changelog A
- ...Object.keys(changelogB), //ALL packages in changelog B
- ]);
-
- const packages = [];
- let changedCount = 0;
- let unchangedCount = 0;
- let onlyInACount = 0;
- let onlyInBCount = 0;
-
- // Helper function to find stable version first, then highest pre-release version
- const findStableVersion = (changelog, packageName, stableVersion) => {
- if (!changelog[packageName]) return null;
-
- const versions = Object.keys(changelog[packageName]);
- if (versions.length === 0) return null;
-
- // Escape dots in version string for regex (3.4.0 -> 3\.4\.0)
- const escapedVersion = stableVersion.replace(/\./g, '\\.');
-
- // Priority 1: Find exact stable version (e.g., "3.4.0" only, no suffixes)
- const exactStablePattern = new RegExp(`^${escapedVersion}$`);
- const exactStableVersion = versions.find((ver) => exactStablePattern.test(ver));
-
- if (exactStableVersion) {
- return exactStableVersion;
- }
-
- // Priority 2: Find oldest pre-release version (any tag: next, alpha, beta, rc, etc.)
- // Pattern: 3.4.0-{tag}.{number} -> captures tag and number
- const prereleasePattern = new RegExp(`^${escapedVersion}-([a-z]+)\\.(\\d+)$`, 'i');
-
- const prereleaseVersions = versions
- .filter((ver) => prereleasePattern.test(ver))
- .sort((a, b) => {
- const matchA = a.match(prereleasePattern);
- const matchB = b.match(prereleasePattern);
- if (!matchA || !matchB) return 0;
- const numA = parseInt(matchA[2], 10);
- const numB = parseInt(matchB[2], 10);
- return numA - numB; // Sort ascending (lowest first)
- });
- //console.log('prereleaseVersions', prereleaseVersions);
- //console.log('versions', versions);
- // Return highest pre-release version, or fallback to first available
- return prereleaseVersions[0] || versions[0];
- };
+/* ============================================
+ COMMIT HISTORY — CROSS-STABLE COLLECTION
+ Walk every stable version between stableA and stableB,
+ open each log file, and collect commits per the rules below.
+ ============================================ */
+
+const sortStableVersions = (versions) =>
+ [...versions].sort((a, b) => {
+ const p = (v) => v.split('.').map(Number);
+ const [aMaj, aMin, aPatch] = p(a);
+ const [bMaj, bMin, bPatch] = p(b);
+ return aMaj !== bMaj ? aMaj - bMaj : aMin !== bMin ? aMin - bMin : aPatch - bPatch;
+ });
- allPackageNames.forEach((packageName) => {
- // Use release version per stable train (exact stable or highest prerelease), not chronologically earliest
- const versionA = findStableVersion(changelogA, packageName, stableVersionA);
- const versionB = findStableVersion(changelogB, packageName, stableVersionB);
-
- let status, changeClass; //Declare variables for status label and CSS class
-
- if (versionA && versionB) {
- //checks if package is in both changelogs
- if (versionA === versionB) {
- //if versionA is the same as versionB, then it is unchanged
- status = 'Unchanged';
- changeClass = 'unchanged';
- unchangedCount++;
- } else {
- status = 'Version Changed';
- changeClass = 'version-changed';
- changedCount++;
- }
- } else if (versionA && !versionB) {
- status = 'Removed';
- changeClass = 'only-in-a';
- onlyInACount++;
- } else if (!versionA && versionB) {
- status = 'Added';
- changeClass = 'only-in-b';
- onlyInBCount++;
- }
+const isPreRelease = (version, stableVersion) => {
+ const escaped = stableVersion.replace(/\./g, '\\.');
+ return new RegExp(`^${escaped}-`).test(version);
+};
- packages.push({
- packageName,
- versionA: versionA || 'N/A',
- versionB: versionB || 'N/A',
- status,
- changeClass,
- });
- });
+const isExactStable = (version) => /^\d+\.\d+\.\d+$/.test(version);
- // Sort packages alphabetically
- packages.sort((a, b) => a.packageName.localeCompare(b.packageName));
+const getPreReleaseNum = (version) => {
+ const match = version.match(/\.(\d+)$/);
+ return match ? parseInt(match[1], 10) : 0;
+};
- return {
- packages,
- totalPackages: allPackageNames.size,
- changedCount,
- unchangedCount,
- onlyInACount,
- onlyInBCount,
- };
+const getPreReleaseTag = (version, stableVersion) => {
+ return version.slice(stableVersion.length + 1).replace(/\.\d+$/, '');
};
-//Data Fetching
-const fetchAndCompareVersions = async (versionA, versionB, versionPaths) => {
- const [changelogA, changelogB] = await Promise.all([
- fetch(versionPaths[versionA]).then((res) => {
- if (!res.ok) throw new Error(`Failed to fetch ${versionA}`);
- return res.json();
- }),
- fetch(versionPaths[versionB]).then((res) => {
- if (!res.ok) throw new Error(`Failed to fetch ${versionB}`);
- return res.json();
- }),
- ]);
-
- // Extract packages from both versions
- const packagesA = extractPackagesFromVersion(changelogA);
- const packagesB = extractPackagesFromVersion(changelogB);
-
- // Compare packages
- const comparisonData = comparePackages(packagesA, packagesB, changelogA, changelogB, versionA, versionB);
+/**
+ * Collect commits from one stable version's package data.
+ *
+ * Rules:
+ * 'start' → from versionA (inclusive) through ALL remaining pre-releases
+ * 'middle' → skip exact stable entry; ALL pre-releases of this stable
+ * 'end' → ALL pre-releases from next.1 up to versionB (inclusive)
+ * 'only' → stableA === stableB; from versionA to versionB within same file
+ */
+const collectCommitsFromStable = (packageData, stableVersion, versionA, versionB, position) => {
+ if (!packageData) return [];
+ const all = Object.keys(packageData);
+ let versionsToUse = [];
+
+ // Determine the target tag from the user-selected versions
+ const targetTag = !isExactStable(versionA)
+ ? getPreReleaseTag(versionA, stableVersion)
+ : !isExactStable(versionB)
+ ? getPreReleaseTag(versionB, stableVersion)
+ : null;
+
+ if (position === 'start') {
+ if (versionA === stableVersion) {
+ versionsToUse = all.filter((v)=>isPreRelease(v,stableVersion));
+ } else {
+ const tagA = getPreReleaseTag(versionA, stableVersion);
+ const numA = getPreReleaseNum(versionA);
+ versionsToUse = all.filter((v) => {
+ if (!isPreRelease(v, stableVersion)) return false;
+ const tag = getPreReleaseTag(v, stableVersion);
+ if (tag !== tagA) return false;
+ const num = getPreReleaseNum(v);
+ return num >= numA;
+ });
+ }
+ } else if (position === 'middle') {
+ versionsToUse =
+ isExactStable(versionA) && isExactStable(versionB)
+ ? []
+ : all.filter((v) => {
+ if (!isPreRelease(v, stableVersion)) return false;
+ if (!targetTag) return true;
+ return getPreReleaseTag(v, stableVersion) === targetTag;
+ });
+ } else if (position === 'end') {
+ if (versionB === stableVersion) {
+ versionsToUse = [stableVersion];
+ } else {
+ const tagB = getPreReleaseTag(versionB, stableVersion);
+ const numB = getPreReleaseNum(versionB);
+ versionsToUse = all.filter((v) => {
+ if (!isPreRelease(v, stableVersion)) return false;
+ const tag = getPreReleaseTag(v, stableVersion);
+ if (tag !== tagB) return false;
+ const num = getPreReleaseNum(v);
+ return num <= numB;
+ });
+ }
+ } else {
+ // 'only' — stableA === stableB
+ if (versionA === stableVersion && versionB === stableVersion) {
+ versionsToUse = [stableVersion];
+ } else if (versionA === stableVersion) {
+ const tagB = getPreReleaseTag(versionB, stableVersion);
+ const numB = getPreReleaseNum(versionB);
+ versionsToUse = all.filter((v) => {
+ if (v === stableVersion) return true;
+ if (!isPreRelease(v, stableVersion)) return false;
+ const tag = getPreReleaseTag(v, stableVersion);
+ if (tag !== tagB) return false;
+ const num = getPreReleaseNum(v);
+ return num <= numB;
+ });
+ } else {
+ const tagA = getPreReleaseTag(versionA, stableVersion);
+ const numA = getPreReleaseNum(versionA);
+ const tagB = getPreReleaseTag(versionB, stableVersion);
+ const numB = getPreReleaseNum(versionB);
+ versionsToUse = all.filter((v) => {
+ if (!isPreRelease(v, stableVersion)) return false;
+ const tag = getPreReleaseTag(v, stableVersion);
+ const num = getPreReleaseNum(v);
+ const afterStart = tag === tagA ? num >= numA : true;
+ const beforeEnd = tag === tagB ? num <= numB : true;
+ return afterStart && beforeEnd;
+ });
+ }
+ }
- return {
- versionA,
- versionB,
- comparisonData,
- };
+ const seen = new Map();
+ versionsToUse.forEach((ver) => {
+ Object.entries(packageData[ver]?.commits || {}).forEach(([hash, message]) => {
+ if (!seen.has(hash)) {
+ seen.set(hash, {
+ hash,
+ shortHash: hash.substring(0, 7),
+ message,
+ version: ver,
+ stableGroup: stableVersion,
+ });
+ }
+ });
+ });
+ return Array.from(seen.values());
};
+
const generatePackageComparisonData = (packageName, versionASpecific, versionBSpecific, changelogA, changelogB) => {
const effectiveVersionA = getEffectiveVersion(changelogA, packageName, versionASpecific);
const effectiveVersionB = getEffectiveVersion(changelogB, packageName, versionBSpecific);
- console.log('effectiveVersionA', effectiveVersionA);
- console.log('effectiveVersionB', effectiveVersionB);
// Get package data from changelogs
const pkgDataA = changelogA[packageName]?.[effectiveVersionA];
const pkgDataB = changelogB[packageName]?.[effectiveVersionB];
- console.log('pkgDataA', pkgDataA);
- console.log('pkgDataB', pkgDataB);
-
// Validate versions exist
if (!pkgDataA && !pkgDataB) {
throw new Error(`Could not find version data for ${packageName}`);
@@ -373,15 +329,7 @@ const generatePackageComparisonData = (packageName, versionASpecific, versionBSp
//Export All the functions
export {
comparisonState,
- extractPackagesFromVersion,
- findLatestPackageVersion,
- getEffectiveVersion,
- getPackageVersion,
- determinePackageStatus,
- createPackageComparisonRow,
- calculateComparisonStats,
- buildPackagesList,
- comparePackages,
- fetchAndCompareVersions,
generatePackageComparisonData,
+ sortStableVersions,
+ collectCommitsFromStable,
};
diff --git a/docs/changelog/index.html b/docs/changelog/index.html
index 18a7eb548..e6e9e5f9f 100644
--- a/docs/changelog/index.html
+++ b/docs/changelog/index.html
@@ -154,9 +154,9 @@
@@ -180,7 +180,7 @@
-
@@ -193,7 +193,7 @@
Search Examples:
-
- Package search - @webex/widgets@1.27.0
+ Package search - @webex/widgets@1.28.2
-
Version Comparison Examples:
How to use version comparison:
- - Select two different stable versions to compare
- - For full comparison: Click "Compare Versions" to see all package differences
- For package-level comparison: Select a specific package and pre-release versions
- - View color-coded changes: ● Version Changed,
- ● Added,
+
- View color-coded changes: ● Version Changed,
+ ● Added,
● Removed
- Copy the comparison link to share with your team
@@ -250,13 +245,12 @@
-
-
-
+
+
@@ -336,7 +330,7 @@
Version Comparison: {{versionA}} vs {{versionB}}
{{/if}}
- {{totalPackages}} Total Packages
+ {{totalPackages}} Total Packages
{{changedCount}} Version Changed
@@ -350,12 +344,14 @@
Version Comparison: {{versionA}} vs {{versionB}}
{{onlyInACount}} Removed in {{versionB}}
-
+ {{#if totalCommits}}
+
+ {{totalCommits}} Commits
+
+ {{/if}}
-
-
@@ -380,6 +376,50 @@ Version Comparison: {{versionA}} vs {{versionB}}
+
+
+