Commit 9d8bc3e9d8 for handsontable.com
commit 9d8bc3e9d8eaa85283e577f52ed32396f9c110a3
Author: Artur Mędrygał <artur.medrygal@handsontable.com>
Date: Fri May 15 10:47:00 2026 +0200
DEV-1678: Fix version tag when building release candidate documentation (#12584)
diff --git a/.github/workflows/docs-staging.yml b/.github/workflows/docs-staging.yml
index a378b16273..b5f575871f 100644
--- a/.github/workflows/docs-staging.yml
+++ b/.github/workflows/docs-staging.yml
@@ -58,7 +58,7 @@ jobs:
if (isReleaseBranch) {
console.log(`Release branch detected: ${ref}`);
console.log(`Target version: ${targetVersion}`);
- console.log('Will build docs in production mode to mimic the final release.');
+ console.log('Will build docs in production mode (GTM/HotJar, no dev badge).');
}
- name: Find PR
@@ -155,23 +155,6 @@ jobs:
pnpm install
npm run all build -- --e examples visual-tests
- # RC release branches: patch handsontable/package.json to the clean target
- # version (e.g., 17.1.0-rc1 -> 17.1.0) so the docs build uses the correct
- # version in CodeSandbox URLs, the version dropdown, and common.json.
- - name: Patch version for RC release branch
- if: ${{ steps.release-info.outputs.is_release == 'true' }}
- working-directory: .
- run: |
- TARGET_VERSION="${{ steps.release-info.outputs.target_version }}"
- echo "Patching handsontable/package.json version to ${TARGET_VERSION}"
- node -e "
- const fs = require('fs');
- const pkg = JSON.parse(fs.readFileSync('handsontable/package.json', 'utf-8'));
- pkg.version = '${TARGET_VERSION}';
- fs.writeFileSync('handsontable/package.json', JSON.stringify(pkg, null, 2) + '\n');
- "
- echo "Version patched: $(node -p "require('./handsontable/package.json').version")"
-
# RC release branches: measure module sizes (same as production workflow).
- name: Update module size measurements
if: ${{ steps.release-info.outputs.is_release == 'true' }}
@@ -184,8 +167,7 @@ jobs:
npm run build
env:
# RC release branches build in production mode to mimic the final
- # production deployment (GTM/HotJar injected, dev badge hidden,
- # correct version in the version dropdown).
+ # production deployment (GTM/HotJar injected, dev badge hidden).
BUILD_MODE: ${{ steps.release-info.outputs.is_release == 'true' && 'production' || '' }}
- name: Build and deploy the preview to Netlify
@@ -233,5 +215,6 @@ jobs:
echo "**Build mode:** production" >> $GITHUB_STEP_SUMMARY
echo "**Preview URL:** ${{ env.NETLIFY_SITE_URL }}/docs" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
- echo "This staging build mimics the production docs deployment for version ${{ steps.release-info.outputs.target_version }}." >> $GITHUB_STEP_SUMMARY
+ echo "This staging build uses production docs settings (BUILD_MODE=production) for release branch \`${{ steps.release-info.outputs.target_version }}\`." >> $GITHUB_STEP_SUMMARY
+ echo "The published \`handsontable\` version from package.json (including any RC tag) is used in examples and CodeSandbox links." >> $GITHUB_STEP_SUMMARY
echo "Use it to verify the docs before the final release." >> $GITHUB_STEP_SUMMARY
diff --git a/docs/.nvmrc b/docs/.nvmrc
index 209e3ef4b6..2bd5a0a98a 100644
--- a/docs/.nvmrc
+++ b/docs/.nvmrc
@@ -1 +1 @@
-20
+22
diff --git a/docs/package.json b/docs/package.json
index c192fb0cab..623746a890 100755
--- a/docs/package.json
+++ b/docs/package.json
@@ -13,7 +13,7 @@
"docs:code-examples:generate-js": "node scripts/transpile-doc-example.mjs",
"build": "npm run docs:api && astro build",
"preview": "astro preview",
- "docs:test:plugins": "node --test src/plugins/__tests__/*.test.mjs",
+ "docs:test:plugins": "node --test src/plugins/__tests__/*.test.mjs src/lib/__tests__/*.test.mjs",
"docs:lint": "eslint --ext .js,.mjs,.ts,.astro src content",
"docs:lint:fix": "eslint --ext .js,.mjs,.ts,.astro src content --fix",
"docs:visual-test": "npx playwright test",
diff --git a/docs/src/components/PageTitle.astro b/docs/src/components/PageTitle.astro
index 060279c36f..bb93ae05de 100644
--- a/docs/src/components/PageTitle.astro
+++ b/docs/src/components/PageTitle.astro
@@ -18,6 +18,8 @@ import { createRequire } from 'module';
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';
+import { shouldShowNewerVersionBanner } from '../lib/newer-version-banner.mjs';
+
const __dirname = dirname(fileURLToPath(import.meta.url));
const require = createRequire(import.meta.url);
@@ -56,7 +58,7 @@ try {
// Network unavailable — banner will be hidden (graceful degradation).
}
-const showNewerBanner = currentMinor !== '' && latestVersion !== '' && currentMinor !== latestVersion;
+const showNewerBanner = shouldShowNewerVersionBanner(currentMinor, latestVersion);
// ── Framework labels ──────────────────────────────────────────────────────────
const FRAMEWORK_LABELS: Record<string, string> = {
diff --git a/docs/src/lib/__tests__/newer-version-banner.test.mjs b/docs/src/lib/__tests__/newer-version-banner.test.mjs
new file mode 100644
index 0000000000..6f2be9313c
--- /dev/null
+++ b/docs/src/lib/__tests__/newer-version-banner.test.mjs
@@ -0,0 +1,32 @@
+import assert from 'node:assert/strict';
+import test from 'node:test';
+import { shouldShowNewerVersionBanner } from '../newer-version-banner.mjs';
+
+test('no banner when current minor is ahead of production latest (RC preview)', () => {
+ assert.equal(shouldShowNewerVersionBanner('17.2', '17.1'), false);
+});
+
+test('banner when current minor is behind production latest', () => {
+ assert.equal(shouldShowNewerVersionBanner('17.0', '17.1'), true);
+});
+
+test('no banner when minors match', () => {
+ assert.equal(shouldShowNewerVersionBanner('17.1', '17.1'), false);
+});
+
+test('no banner when current is empty', () => {
+ assert.equal(shouldShowNewerVersionBanner('', '17.1'), false);
+});
+
+test('no banner when latest is empty', () => {
+ assert.equal(shouldShowNewerVersionBanner('17.1', ''), false);
+});
+
+test('no banner for next', () => {
+ assert.equal(shouldShowNewerVersionBanner('next', '17.1'), false);
+ assert.equal(shouldShowNewerVersionBanner('17.1', 'next'), false);
+});
+
+test('banner for several-minor gap', () => {
+ assert.equal(shouldShowNewerVersionBanner('16.0', '18.0'), true);
+});
diff --git a/docs/src/lib/newer-version-banner.mjs b/docs/src/lib/newer-version-banner.mjs
new file mode 100644
index 0000000000..2a9f5fc5b2
--- /dev/null
+++ b/docs/src/lib/newer-version-banner.mjs
@@ -0,0 +1,38 @@
+import semver from 'semver';
+
+/**
+ * Whether to show the "newer version available" banner on docs pages.
+ *
+ * The banner is shown only when production's latest **minor** line is strictly
+ * newer than this build's minor (user is viewing older docs). If this build
+ * is ahead of production (e.g. RC for the next minor), returns false.
+ *
+ * @param {string} currentMinor - Major.minor from local package.json (e.g. "17.1").
+ * @param {string} latestVersion - `latestVersion` from common.json (minor line).
+ * @returns {boolean}
+ */
+export function shouldShowNewerVersionBanner(currentMinor, latestVersion) {
+ if (!currentMinor || !latestVersion) {
+ return false;
+ }
+
+ const cur = String(currentMinor).trim();
+ const lat = String(latestVersion).trim();
+
+ if (!cur || !lat) {
+ return false;
+ }
+
+ if (cur.toLowerCase() === 'next' || lat.toLowerCase() === 'next') {
+ return false;
+ }
+
+ const currentCoerced = semver.coerce(`${cur}.0`);
+ const latestCoerced = semver.coerce(`${lat}.0`);
+
+ if (!currentCoerced || !latestCoerced) {
+ return false;
+ }
+
+ return semver.gt(latestCoerced, currentCoerced);
+}