This is an automated email from the ASF dual-hosted git repository.
anmolnar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/zookeeper.git
The following commit(s) were added to refs/heads/master by this push:
new a8f5478ac ZOOKEEPER-5061: ZooKeeper website fixes
a8f5478ac is described below
commit a8f5478aca1eede86874446494118ea1f45e8c46
Author: Yurii Palamarchuk <[email protected]>
AuthorDate: Wed Jul 1 18:34:56 2026 +0200
ZOOKEEPER-5061: ZooKeeper website fixes
Reviewers: anmolnar
Author: yuriipalam
Closes #2413 from yuriipalam/ZOOKEEPER-5061
---
.github/workflows/website.yaml | 11 +-
NOTICE.txt | 1 -
conf/zoo_sample.cfg | 2 +-
pom.xml | 3 -
zookeeper-client/zookeeper-client-c/README | 8 +-
.../zkui/src/zkui/templates/tree.mako | 2 +-
.../zookeeper-contrib-monitoring/README | 2 +-
zookeeper-recipes/README.txt | 5 +-
.../zookeeper-recipes-election/README.txt | 2 +-
.../zookeeper-recipes-lock/README.txt | 2 +-
.../zookeeper-recipes-queue/README.txt | 2 +-
.../main/java/org/apache/zookeeper/ZooKeeper.java | 2 +-
.../server/auth/KeyAuthenticationProvider.java | 2 +-
.../server/command/FourLetterCommands.java | 36 +--
.../zookeeper/server/quorum/QuorumPeerMain.java | 4 +-
zookeeper-website/.gitignore | 6 +
.../app/components/docs/archive-docs-banner.tsx | 78 -------
.../app/components/getting-started.tsx | 2 +-
zookeeper-website/app/components/links.ts | 17 +-
zookeeper-website/app/lib/current-version.ts | 2 +-
zookeeper-website/app/lib/docs-archive.ts | 4 -
.../app/lib/released-docs-versions.ts | 59 ++---
zookeeper-website/app/pages/_docs/docs-layout.tsx | 2 -
zookeeper-website/app/pages/_docs/docs/index.tsx | 3 +-
.../app/pages/_landing/credits/developers.json | 47 ----
.../app/pages/_landing/credits/index.tsx | 48 ++--
.../app/pages/_landing/home/community.tsx | 14 +-
.../app/pages/_landing/security/content.md | 4 +-
.../app/pages/_landing/{irc => slack}/content.md | 12 +-
.../app/pages/_landing/{irc => slack}/index.tsx | 2 +-
zookeeper-website/app/routes.ts | 8 +-
zookeeper-website/app/routes/_api/llms-full.ts | 3 -
.../app/routes/_docs/doc-redirect.tsx | 9 +-
.../app/routes/_docs/{docs.tsx => doc.tsx} | 4 +-
.../app/routes/_landing/{irc.tsx => slack.tsx} | 12 +-
zookeeper-website/e2e-tests/constants.ts | 5 -
.../e2e-tests/older-docs-picker.spec.ts | 162 ++-----------
zookeeper-website/package-lock.json | 2 +-
zookeeper-website/package.json | 10 +-
zookeeper-website/pom.xml | 2 +-
zookeeper-website/public/.htaccess | 2 +
zookeeper-website/public/sitemap.xml | 1 -
zookeeper-website/scripts/build-docs.ts | 67 +-----
zookeeper-website/scripts/build-site.ts | 3 +
.../index.tsx => scripts/extract-developers.d.ts} | 14 +-
zookeeper-website/scripts/extract-developers.js | 113 +++++++++
zookeeper-website/scripts/generate-sitemap.ts | 6 +-
.../unit-tests/archive-docs-banner.test.tsx | 82 -------
...ild-docs-archive.test.ts => build-docs.test.ts} | 10 +-
.../unit-tests/extract-developers.test.ts | 255 +++++++++++++++++++++
zookeeper-website/unit-tests/llms-full.test.ts | 10 +
.../unit-tests/older-docs-picker.test.tsx | 17 +-
.../unit-tests/released-docs-versions.test.ts | 42 +---
53 files changed, 589 insertions(+), 634 deletions(-)
diff --git a/.github/workflows/website.yaml b/.github/workflows/website.yaml
index a51101308..c5528bc36 100644
--- a/.github/workflows/website.yaml
+++ b/.github/workflows/website.yaml
@@ -32,15 +32,15 @@ jobs:
timeout-minutes: 120
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v6
- - name: Set up JDK 25
+ - uses: actions/checkout@v7
+ - name: Set up JDK 11
uses: actions/setup-java@v5
with:
java-version: 25
distribution: temurin
cache: 'maven'
- name: Set up Node.js 22
- uses: actions/setup-node@v5
+ uses: actions/setup-node@v6
with:
node-version: 22
cache: 'npm'
@@ -50,6 +50,11 @@ jobs:
- name: Install website npm dependencies
working-directory: zookeeper-website
run: npm ci
+ - name: Cache Playwright browsers
+ uses: actions/cache@v4
+ with:
+ path: ~/.cache/ms-playwright
+ key: playwright-${{ runner.os }}-${{
hashFiles('zookeeper-website/package-lock.json') }}
- name: Install Playwright browsers and system dependencies
working-directory: zookeeper-website
run: npx playwright install --with-deps
diff --git a/NOTICE.txt b/NOTICE.txt
index 9b3afcb47..3acf69dda 100644
--- a/NOTICE.txt
+++ b/NOTICE.txt
@@ -18,7 +18,6 @@ These BSD licensed files:
./zookeeper-client/zookeeper-client-c/src/hashtable/hashtable_itr.c
./zookeeper-client/zookeeper-client-c/src/hashtable/hashtable_itr.h
./zookeeper-client/zookeeper-client-c/src/hashtable/hashtable_private.h
- ./zookeeper-docs/src/main/resources/markdown/skin/prototype.js
This Apache 2.0 licensed file:
./zookeeper-contrib/zookeeper-contrib-zooinspector/src/main/java/com/nitido/utils/toaster/Toaster.java
diff --git a/conf/zoo_sample.cfg b/conf/zoo_sample.cfg
index d0db6e99a..c70eb842d 100644
--- a/conf/zoo_sample.cfg
+++ b/conf/zoo_sample.cfg
@@ -19,7 +19,7 @@ clientPort=2181
# Be sure to read the maintenance section of the
# administrator guide before turning on autopurge.
#
-# https://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
+#
https://zookeeper.apache.org/doc/current/admin-ops/administrators-guide/administration#maintenance
#
# The number of snapshots to retain in dataDir
#autopurge.snapRetainCount=3
diff --git a/pom.xml b/pom.xml
index 1aacd272b..5a1b50224 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1094,9 +1094,6 @@
<exclude>.travis.yml</exclude>
<exclude>excludeFindBugsFilter.xml</exclude>
<exclude>README_packaging.md</exclude>
- <exclude>src/main/resources/markdown/skin/*</exclude>
- <exclude>src/main/resources/markdown/html/*</exclude>
- <exclude>src/main/resources/markdown/images/*</exclude>
<exclude>**/src/test/resources/embedded/*.conf</exclude>
<!-- contrib -->
<exclude>**/JMX-RESOURCES</exclude>
diff --git a/zookeeper-client/zookeeper-client-c/README
b/zookeeper-client/zookeeper-client-c/README
index 24869756b..a31c6aa06 100644
--- a/zookeeper-client/zookeeper-client-c/README
+++ b/zookeeper-client/zookeeper-client-c/README
@@ -8,7 +8,8 @@ For the latest information about ZooKeeper, please visit our
website at:
and our wiki, at:
https://cwiki.apache.org/confluence/display/ZOOKEEPER
-Full documentation for this release can also be found in ../../docs/index.html
+Full documentation for this release can also be found at:
+ https://zookeeper.apache.org/doc/current/
OVERVIEW
@@ -34,8 +35,9 @@ Sync and Async API.
INSTALLATION
-Please refer to the "Installation" item under "C Binding" section in file
-".../trunk/zookeeper-docs/src/main/resources/markdown/zookeeperProgrammers.md"
+Please refer to the "Installation" item under "C Binding" in the
+Programmer's Guide:
+
https://zookeeper.apache.org/doc/current/developer/programmers-guide/bindings#installation
EXAMPLE/SAMPLE C CLIENT SHELL
diff --git
a/zookeeper-contrib/zookeeper-contrib-huebrowser/zkui/src/zkui/templates/tree.mako
b/zookeeper-contrib/zookeeper-contrib-huebrowser/zkui/src/zkui/templates/tree.mako
index 07c91c331..814676257 100644
---
a/zookeeper-contrib/zookeeper-contrib-huebrowser/zkui/src/zkui/templates/tree.mako
+++
b/zookeeper-contrib/zookeeper-contrib-huebrowser/zkui/src/zkui/templates/tree.mako
@@ -69,7 +69,7 @@ ${shared.header("ZooKeeper Browser > Tree > %s > %s" %
(cluster['nice_name'], pa
</table>
<br />
-<a target="_blank" rel="noopener noreferrer"
href="http://zookeeper.apache.org/docs/current/zookeeperProgrammers.html#sc_zkStatStructure">Details
on stat information.</a>
+<a target="_blank" rel="noopener noreferrer"
href="https://zookeeper.apache.org/doc/current/developer/programmers-guide/data-model#zookeeper-stat-structure">Details
on stat information.</a>
${shared.footer()}
diff --git a/zookeeper-contrib/zookeeper-contrib-monitoring/README
b/zookeeper-contrib/zookeeper-contrib-monitoring/README
index 81521fcb0..44f7b2cef 100644
--- a/zookeeper-contrib/zookeeper-contrib-monitoring/README
+++ b/zookeeper-contrib/zookeeper-contrib-monitoring/README
@@ -81,5 +81,5 @@ Apache License 2.0 or later.
ZooKeeper 4letterwords Commands
-------------------------------
-http://zookeeper.apache.org/docs/current/zookeeperAdmin.html#sc_zkCommands
+https://zookeeper.apache.org/doc/current/admin-ops/administrators-guide/commands#the-four-letter-words
diff --git a/zookeeper-recipes/README.txt b/zookeeper-recipes/README.txt
index bc788741d..db41fa0d4 100644
--- a/zookeeper-recipes/README.txt
+++ b/zookeeper-recipes/README.txt
@@ -13,8 +13,9 @@ some unit testing with both the c and java recipe code.
zkr_recipe-name_methodname
(eg. zkr_lock_lock in zookeeper-recipes-lock/src/c)
-6) The various recipes are in ../docs/recipes.html or
-../../docs/reciped.pdf. Also, this is not an exhaustive list by any chance.
+6) The various recipes are documented at
+ https://zookeeper.apache.org/doc/current/developer/recipes
+ Also, this is not an exhaustive list by any chance.
Zookeeper is used (and can be used) for more than what we have listed in the
docs.
7) To run the c tests in all the recipes,
diff --git a/zookeeper-recipes/zookeeper-recipes-election/README.txt
b/zookeeper-recipes/zookeeper-recipes-election/README.txt
index f854b275e..3de76293a 100644
--- a/zookeeper-recipes/zookeeper-recipes-election/README.txt
+++ b/zookeeper-recipes/zookeeper-recipes-election/README.txt
@@ -16,7 +16,7 @@
-->
1) This election interface recipe implements the leader election recipe
-mentioned in ../../docs/recipes.[html,pdf].
+documented at https://zookeeper.apache.org/doc/current/developer/recipes
2) To compile the leader election java recipe you can just run ant jar from
this directory.
diff --git a/zookeeper-recipes/zookeeper-recipes-lock/README.txt
b/zookeeper-recipes/zookeeper-recipes-lock/README.txt
index 438de1bdb..e4c63081a 100644
--- a/zookeeper-recipes/zookeeper-recipes-lock/README.txt
+++ b/zookeeper-recipes/zookeeper-recipes-lock/README.txt
@@ -16,7 +16,7 @@
-->
1) This lock interface recipe implements the lock recipe
-mentioned in ../../docs/recipes.[html,pdf].
+documented at https://zookeeper.apache.org/doc/current/developer/recipes
2) To compile the lock java recipe you can just run ant jar from
this directory. For compiling the c library go to
zookeeper-client/zookeeper-client-c and read
diff --git a/zookeeper-recipes/zookeeper-recipes-queue/README.txt
b/zookeeper-recipes/zookeeper-recipes-queue/README.txt
index d59a3c317..49fb6f546 100644
--- a/zookeeper-recipes/zookeeper-recipes-queue/README.txt
+++ b/zookeeper-recipes/zookeeper-recipes-queue/README.txt
@@ -16,7 +16,7 @@
-->
1) This queue interface recipe implements the queue recipe
-mentioned in ../../../docs/recipes.[html,pdf].
+documented at https://zookeeper.apache.org/doc/current/developer/recipes
A more detailed explanation is at
http://www.cloudera.com/blog/2009/05/28/building-a-distributed-concurrent-queue-with-apache-zookeeper/
2) This recipe does not handle KeeperException.ConnectionLossException or
ZCONNECTIONLOSS. It will only work correctly once ZOOKEEPER-22
https://issues.apache.org/jira/browse/ZOOKEEPER-22 is resolved.
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/ZooKeeper.java
b/zookeeper-server/src/main/java/org/apache/zookeeper/ZooKeeper.java
index 32893d46a..239f97b0f 100644
--- a/zookeeper-server/src/main/java/org/apache/zookeeper/ZooKeeper.java
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/ZooKeeper.java
@@ -1718,7 +1718,7 @@ public void delete(final String path, int version) throws
InterruptedException,
* Note: The maximum allowable size of all of the data arrays in all of
* the setData operations in this single request is typically 1 MB
* (1,048,576 bytes). This limit is specified on the server via
- * <a
href="http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#Unsafe+Options">jute.maxbuffer</a>.
+ * <a
href="https://zookeeper.apache.org/doc/current/admin-ops/administrators-guide/configuration-parameters#unsafe-options">jute.maxbuffer</a>.
* Requests larger than this will cause a KeeperException to be
* thrown.
*
diff --git
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/KeyAuthenticationProvider.java
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/KeyAuthenticationProvider.java
index cf252b9b0..92059bef5 100644
---
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/KeyAuthenticationProvider.java
+++
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/KeyAuthenticationProvider.java
@@ -37,7 +37,7 @@
* See the "Pluggable ZooKeeper authentication" section of the
* "Zookeeper Programmer's Guide" for general details of implementing an
* authentication plugin. e.g.
- *
http://zookeeper.apache.org/doc/current/zookeeperProgrammers.html#sc_ZooKeeperPluggableAuthentication
+ *
https://zookeeper.apache.org/doc/current/developer/programmers-guide/pluggable-authentication
*
* This class looks for a numeric "key" under the /key node.
* Authorization is granted if the user passes in as authorization a number
diff --git
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/command/FourLetterCommands.java
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/command/FourLetterCommands.java
index 3c615f4fa..6566cdaf3 100644
---
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/command/FourLetterCommands.java
+++
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/command/FourLetterCommands.java
@@ -33,108 +33,108 @@
public class FourLetterCommands {
/*
- * See <a
href="{@docRoot}/../../../docs/zookeeperAdmin.html#sc_zkCommands">
+ * See <a
href="{@docRoot}/../../admin-ops/administrators-guide/commands#the-four-letter-words">
* Zk Admin</a>. this link is for all the commands.
*/
public static final int confCmd =
ByteBuffer.wrap("conf".getBytes()).getInt();
/*
- * See <a
href="{@docRoot}/../../../docs/zookeeperAdmin.html#sc_zkCommands">
+ * See <a
href="{@docRoot}/../../admin-ops/administrators-guide/commands#the-four-letter-words">
* Zk Admin</a>. this link is for all the commands.
*/
public static final int consCmd =
ByteBuffer.wrap("cons".getBytes()).getInt();
/*
- * See <a
href="{@docRoot}/../../../docs/zookeeperAdmin.html#sc_zkCommands">
+ * See <a
href="{@docRoot}/../../admin-ops/administrators-guide/commands#the-four-letter-words">
* Zk Admin</a>. this link is for all the commands.
*/
public static final int crstCmd =
ByteBuffer.wrap("crst".getBytes()).getInt();
/*
- * See <a
href="{@docRoot}/../../../docs/zookeeperAdmin.html#sc_zkCommands">
+ * See <a
href="{@docRoot}/../../admin-ops/administrators-guide/commands#the-four-letter-words">
* Zk Admin</a>. this link is for all the commands.
*/
public static final int dirsCmd =
ByteBuffer.wrap("dirs".getBytes()).getInt();
/*
- * See <a
href="{@docRoot}/../../../docs/zookeeperAdmin.html#sc_zkCommands">
+ * See <a
href="{@docRoot}/../../admin-ops/administrators-guide/commands#the-four-letter-words">
* Zk Admin</a>. this link is for all the commands.
*/
public static final int dumpCmd =
ByteBuffer.wrap("dump".getBytes()).getInt();
/*
- * See <a
href="{@docRoot}/../../../docs/zookeeperAdmin.html#sc_zkCommands">
+ * See <a
href="{@docRoot}/../../admin-ops/administrators-guide/commands#the-four-letter-words">
* Zk Admin</a>. this link is for all the commands.
*/
public static final int enviCmd =
ByteBuffer.wrap("envi".getBytes()).getInt();
/*
- * See <a
href="{@docRoot}/../../../docs/zookeeperAdmin.html#sc_zkCommands">
+ * See <a
href="{@docRoot}/../../admin-ops/administrators-guide/commands#the-four-letter-words">
* Zk Admin</a>. this link is for all the commands.
*/
public static final int getTraceMaskCmd =
ByteBuffer.wrap("gtmk".getBytes()).getInt();
/*
- * See <a
href="{@docRoot}/../../../docs/zookeeperAdmin.html#sc_zkCommands">
+ * See <a
href="{@docRoot}/../../admin-ops/administrators-guide/commands#the-four-letter-words">
* Zk Admin</a>. this link is for all the commands.
*/
public static final int ruokCmd =
ByteBuffer.wrap("ruok".getBytes()).getInt();
/*
- * See <a
href="{@docRoot}/../../../docs/zookeeperAdmin.html#sc_zkCommands">
+ * See <a
href="{@docRoot}/../../admin-ops/administrators-guide/commands#the-four-letter-words">
* Zk Admin</a>. this link is for all the commands.
*/
public static final int setTraceMaskCmd =
ByteBuffer.wrap("stmk".getBytes()).getInt();
/*
- * See <a
href="{@docRoot}/../../../docs/zookeeperAdmin.html#sc_zkCommands">
+ * See <a
href="{@docRoot}/../../admin-ops/administrators-guide/commands#the-four-letter-words">
* Zk Admin</a>. this link is for all the commands.
*/
public static final int srvrCmd =
ByteBuffer.wrap("srvr".getBytes()).getInt();
/*
- * See <a
href="{@docRoot}/../../../docs/zookeeperAdmin.html#sc_zkCommands">
+ * See <a
href="{@docRoot}/../../admin-ops/administrators-guide/commands#the-four-letter-words">
* Zk Admin</a>. this link is for all the commands.
*/
public static final int srstCmd =
ByteBuffer.wrap("srst".getBytes()).getInt();
/*
- * See <a
href="{@docRoot}/../../../docs/zookeeperAdmin.html#sc_zkCommands">
+ * See <a
href="{@docRoot}/../../admin-ops/administrators-guide/commands#the-four-letter-words">
* Zk Admin</a>. this link is for all the commands.
*/
public static final int statCmd =
ByteBuffer.wrap("stat".getBytes()).getInt();
/*
- * See <a
href="{@docRoot}/../../../docs/zookeeperAdmin.html#sc_zkCommands">
+ * See <a
href="{@docRoot}/../../admin-ops/administrators-guide/commands#the-four-letter-words">
* Zk Admin</a>. this link is for all the commands.
*/
public static final int wchcCmd =
ByteBuffer.wrap("wchc".getBytes()).getInt();
/*
- * See <a
href="{@docRoot}/../../../docs/zookeeperAdmin.html#sc_zkCommands">
+ * See <a
href="{@docRoot}/../../admin-ops/administrators-guide/commands#the-four-letter-words">
* Zk Admin</a>. this link is for all the commands.
*/
public static final int wchpCmd =
ByteBuffer.wrap("wchp".getBytes()).getInt();
/*
- * See <a
href="{@docRoot}/../../../docs/zookeeperAdmin.html#sc_zkCommands">
+ * See <a
href="{@docRoot}/../../admin-ops/administrators-guide/commands#the-four-letter-words">
* Zk Admin</a>. this link is for all the commands.
*/
public static final int wchsCmd =
ByteBuffer.wrap("wchs".getBytes()).getInt();
/*
- * See <a
href="{@docRoot}/../../../docs/zookeeperAdmin.html#sc_zkCommands">
+ * See <a
href="{@docRoot}/../../admin-ops/administrators-guide/commands#the-four-letter-words">
* Zk Admin</a>. this link is for all the commands.
*/
public static final int mntrCmd =
ByteBuffer.wrap("mntr".getBytes()).getInt();
/*
- * See <a
href="{@docRoot}/../../../docs/zookeeperAdmin.html#sc_zkCommands">
+ * See <a
href="{@docRoot}/../../admin-ops/administrators-guide/commands#the-four-letter-words">
* Zk Admin</a>. this link is for all the commands.
*/
public static final int isroCmd =
ByteBuffer.wrap("isro".getBytes()).getInt();
/*
- * See <a
href="{@docRoot}/../../../docs/zookeeperAdmin.html#sc_zkCommands">
+ * See <a
href="{@docRoot}/../../admin-ops/administrators-guide/commands#the-four-letter-words">
* Zk Admin</a>. this link is for all the commands.
*/
protected static final int hashCmd =
ByteBuffer.wrap("hash".getBytes()).getInt();
diff --git
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/QuorumPeerMain.java
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/QuorumPeerMain.java
index c126ea2e2..86b3b8a7c 100644
---
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/QuorumPeerMain.java
+++
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/QuorumPeerMain.java
@@ -52,8 +52,8 @@
* configuration information. This file is a Properties file, so keys and
* values are separated by equals (=) and the key/value pairs are separated
* by new lines. The following is a general summary of keys used in the
- * configuration file. For full details on this see the documentation in
- * docs/index.html
+ * configuration file. For full details see the
+ * <a
href="https://zookeeper.apache.org/doc/current/admin-ops/administrators-guide/configuration-parameters">Administrator's
Guide</a>.
* <ol>
* <li>dataDir - The directory where the ZooKeeper data is stored.</li>
* <li>dataLogDir - The directory where the ZooKeeper transaction log is
stored.</li>
diff --git a/zookeeper-website/.gitignore b/zookeeper-website/.gitignore
index 583284939..66bf0a907 100644
--- a/zookeeper-website/.gitignore
+++ b/zookeeper-website/.gitignore
@@ -41,3 +41,9 @@ node_modules/
/blob-report/
/playwright/.cache/
/playwright/.auth/
+
+# Generated developer list (see scripts/extract-developers.js)
+/app/pages/_landing/credits/developers.json
+
+# Generated sitemap (see scripts/generate-sitemap.ts)
+/public/sitemap.xml
diff --git a/zookeeper-website/app/components/docs/archive-docs-banner.tsx
b/zookeeper-website/app/components/docs/archive-docs-banner.tsx
deleted file mode 100644
index ba78c0bbd..000000000
--- a/zookeeper-website/app/components/docs/archive-docs-banner.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-//
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements. See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership. The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License. You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-import { ArrowRight, X } from "lucide-react";
-import { useEffect, useState } from "react";
-import { CURRENT_VERSION } from "@/lib/current-version";
-import { DOCS_ARCHIVE_SNAPSHOT_ENV } from "@/lib/docs-archive";
-import { Button } from "@/ui/button";
-
-const bannerStorageKey =
`zookeeper-docs-archive-banner-dismissed:${CURRENT_VERSION}`;
-
-export function ArchiveDocsBanner() {
- // Set only by `build:docs-archive`, never by the current docs build or dev.
- const isArchiveDocsView = import.meta.env[DOCS_ARCHIVE_SNAPSHOT_ENV] === "1";
- const [visible, setVisible] = useState(false);
-
- useEffect(() => {
- if (!isArchiveDocsView) {
- return;
- }
-
- setVisible(localStorage.getItem(bannerStorageKey) !== "1");
- }, [isArchiveDocsView]);
-
- if (!isArchiveDocsView || !visible) {
- return null;
- }
-
- const dismiss = () => {
- localStorage.setItem(bannerStorageKey, "1");
- setVisible(false);
- };
-
- return (
- <div
- role="status"
- className="bg-muted text-foreground border-b px-4 py-2 text-sm"
- >
- <div className="flex items-center gap-4">
- <p className="mx-auto text-center">
- You are viewing the v{CURRENT_VERSION} docs.{" "}
- <a
- href="/doc/"
- className="inline-flex items-center gap-1 font-medium underline
underline-offset-4"
- >
- Switch to latest
- <ArrowRight className="h-4 w-4" aria-hidden />
- </a>
- </p>
- <Button
- type="button"
- variant="ghost"
- size="icon"
- className="size-8 shrink-0"
- aria-label="Dismiss archived docs notice"
- onClick={dismiss}
- >
- <X className="h-4 w-4" />
- </Button>
- </div>
- </div>
- );
-}
diff --git a/zookeeper-website/app/components/getting-started.tsx
b/zookeeper-website/app/components/getting-started.tsx
index 633c8b7c5..fae43f7d4 100644
--- a/zookeeper-website/app/components/getting-started.tsx
+++ b/zookeeper-website/app/components/getting-started.tsx
@@ -35,7 +35,7 @@ export function GettingStartedSection() {
{
title: "3. Write a Client",
desc: "Follow the basic tutorial to implement distributed primitives
like barriers and queues.",
- to: `${CURRENT_DOCS_PATH}/developer/basic-tutorial`
+ to: "https://cwiki.apache.org/confluence/display/ZOOKEEPER/Tutorial"
}
];
return (
diff --git a/zookeeper-website/app/components/links.ts
b/zookeeper-website/app/components/links.ts
index 6f7ca909e..97fa3d8be 100644
--- a/zookeeper-website/app/components/links.ts
+++ b/zookeeper-website/app/components/links.ts
@@ -16,8 +16,7 @@
// limitations under the License.
//
-import { CURRENT_VERSION } from "@/lib/current-version";
-import { CURRENT_DOCS_PATH } from "@/lib/docs-paths";
+import { getReleasedDocUrl, LTS_VERSIONS } from "@/lib/released-docs-versions";
interface LinkType {
label: string;
@@ -62,10 +61,12 @@ export const projectLinks: LinkType[] = [
];
export const documentationLinks: (LinkType | NestedLinkType)[] = [
- {
- label: `${CURRENT_VERSION} Documentation`,
- to: CURRENT_DOCS_PATH
- },
+ ...LTS_VERSIONS.map(
+ (version): LinkType => ({
+ label: `${version} Documentation`,
+ to: getReleasedDocUrl(version)
+ })
+ ),
{
label: "Issue Tracking",
to: "https://issues.apache.org/jira/browse/ZOOKEEPER",
@@ -88,8 +89,8 @@ export const documentationLinks: (LinkType |
NestedLinkType)[] = [
external: true
},
{
- label: "IRC Channel",
- to: "/irc"
+ label: "Slack Channel",
+ to: "/slack"
}
]
}
diff --git a/zookeeper-website/app/lib/current-version.ts
b/zookeeper-website/app/lib/current-version.ts
index d02009316..c1c6461e0 100644
--- a/zookeeper-website/app/lib/current-version.ts
+++ b/zookeeper-website/app/lib/current-version.ts
@@ -16,4 +16,4 @@
// limitations under the License.
//
-export const CURRENT_VERSION = "3.9.5";
+export const CURRENT_VERSION = "3.10.0";
diff --git a/zookeeper-website/app/lib/docs-archive.ts
b/zookeeper-website/app/lib/docs-archive.ts
index 625ca8d07..6744e3730 100644
--- a/zookeeper-website/app/lib/docs-archive.ts
+++ b/zookeeper-website/app/lib/docs-archive.ts
@@ -52,7 +52,3 @@ export function getBuildTarget(): "landing" | undefined {
? "landing"
: undefined;
}
-
-// Marks a docs build as an archived snapshot of an older release. The VITE_
-// prefix intentionally exposes it to bundled browser code via import.meta.env.
-export const DOCS_ARCHIVE_SNAPSHOT_ENV =
"VITE_ZOOKEEPER_DOCS_ARCHIVE_SNAPSHOT";
diff --git a/zookeeper-website/app/lib/released-docs-versions.ts
b/zookeeper-website/app/lib/released-docs-versions.ts
index 382953df9..53a901005 100644
--- a/zookeeper-website/app/lib/released-docs-versions.ts
+++ b/zookeeper-website/app/lib/released-docs-versions.ts
@@ -16,6 +16,8 @@
// limitations under the License.
//
+import { CURRENT_VERSION } from "./current-version";
+
type PreRelease = "alpha" | "beta" | "stable";
interface ParsedVersion {
@@ -47,7 +49,7 @@ const preReleaseOrder: Record<PreRelease, number> = {
alpha: 0
};
-export const LEGACY_RELEASED_DOC_VERSIONS = new Set([
+const RAW_RELEASED_DOC_VERSIONS_LIST = [
"3.1.2",
"3.2.2",
"3.3.2",
@@ -99,16 +101,22 @@ export const LEGACY_RELEASED_DOC_VERSIONS = new Set([
"3.9.1",
"3.9.2",
"3.9.3",
- "3.9.4"
-]);
+ "3.9.4",
+ "3.9.5",
+ CURRENT_VERSION
+] as const;
-export const REACT_ROUTER_RELEASED_DOC_VERSIONS = new Set<string>();
+export type ReleasedDocVersion =
+ (typeof RAW_RELEASED_DOC_VERSIONS_LIST)[number];
+
+export const RAW_RELEASED_DOC_VERSIONS = new Set<ReleasedDocVersion>(
+ RAW_RELEASED_DOC_VERSIONS_LIST
+);
-export const RAW_RELEASED_DOC_VERSIONS: string[] = [
- ...new Set([
- ...LEGACY_RELEASED_DOC_VERSIONS,
- ...REACT_ROUTER_RELEASED_DOC_VERSIONS
- ])
+export const LTS_VERSIONS: ReleasedDocVersion[] = [
+ CURRENT_VERSION,
+ "3.9.5",
+ "3.8.6"
];
export function sortVersionsDesc(versions: string[]): string[] {
@@ -126,36 +134,15 @@ export function sortVersionsDesc(versions: string[]):
string[] {
* All released documentation versions available under /doc/.
* Maintained manually because archived docs live in the asf-site branch.
*/
-export const RELEASED_DOC_VERSIONS: string[] = sortVersionsDesc(
- RAW_RELEASED_DOC_VERSIONS
-);
+export const RELEASED_DOC_VERSIONS: string[] = sortVersionsDesc([
+ ...RAW_RELEASED_DOC_VERSIONS
+]);
export function getReleasedDocUrl(version: string): string {
- const basePath = `/doc/r${version}`;
- return LEGACY_RELEASED_DOC_VERSIONS.has(version)
- ? `${basePath}/index.html`
- : `${basePath}/`;
+ return `/doc/r${version}/`;
}
export function getReleasedDocVersions(): string[] {
- if (typeof window !== "undefined") {
- const override = window.localStorage.getItem(
- "__released_doc_versions_override__"
- );
- if (override) {
- try {
- const parsed = JSON.parse(override);
- if (
- Array.isArray(parsed) &&
- parsed.every((value) => typeof value === "string")
- ) {
- return sortVersionsDesc(parsed);
- }
- } catch {
- // Ignore invalid test overrides and fall back to build-time data.
- }
- }
- }
-
- return RELEASED_DOC_VERSIONS;
+ const ltsSet = new Set<string>(LTS_VERSIONS);
+ return RELEASED_DOC_VERSIONS.filter((v) => !ltsSet.has(v));
}
diff --git a/zookeeper-website/app/pages/_docs/docs-layout.tsx
b/zookeeper-website/app/pages/_docs/docs-layout.tsx
index d809707cb..cadfd4b4a 100644
--- a/zookeeper-website/app/pages/_docs/docs-layout.tsx
+++ b/zookeeper-website/app/pages/_docs/docs-layout.tsx
@@ -18,7 +18,6 @@
import { RootProvider } from "fumadocs-ui/provider/react-router";
import { Outlet } from "react-router";
-import { ArchiveDocsBanner } from "@/components/docs/archive-docs-banner";
import { SearchDialog } from "@/components/docs/search/docs-search";
export default function DocsLayout() {
@@ -28,7 +27,6 @@ export default function DocsLayout() {
SearchDialog
}}
>
- <ArchiveDocsBanner />
<Outlet />
</RootProvider>
);
diff --git a/zookeeper-website/app/pages/_docs/docs/index.tsx
b/zookeeper-website/app/pages/_docs/docs/index.tsx
index f161a819f..256418539 100644
--- a/zookeeper-website/app/pages/_docs/docs/index.tsx
+++ b/zookeeper-website/app/pages/_docs/docs/index.tsx
@@ -38,6 +38,7 @@ import { OlderDocsPicker } from
"@/components/docs/older-docs-picker";
import type { MDXComponents } from "mdx/types";
import { getDocsBasePath, resolveDocsHref } from "@/lib/docs-paths";
import { SITE_URL } from "@/lib/site";
+import { CURRENT_VERSION } from "@/lib/current-version";
// Extend default MDX components to include shared UI blocks globally.
// Note: We'll override the 'a' component in the renderer to handle
route-specific logic
@@ -66,7 +67,7 @@ export function baseOptions(): BaseLayoutProps {
width={16}
height={16}
/>
- <p>Apache ZooKeeper</p>
+ <p>Apache ZooKeeper {CURRENT_VERSION}</p>
</div>
)
},
diff --git a/zookeeper-website/app/pages/_landing/credits/developers.json
b/zookeeper-website/app/pages/_landing/credits/developers.json
deleted file mode 100644
index f4a77670e..000000000
--- a/zookeeper-website/app/pages/_landing/credits/developers.json
+++ /dev/null
@@ -1,47 +0,0 @@
-{
- "pmc": [
- { "id": "tdunning", "name": "Ted Dunning", "organization": "MapR
Technologies", "timezone": "-8" },
- { "id": "camille", "name": "Camille Fournier", "organization":
"RentTheRunway", "timezone": "-5" },
- { "id": "phunt", "name": "Patrick Hunt", "organization": "Cloudera Inc.",
"timezone": "-8" },
- { "id": "fpj", "name": "Flavio Junqueira", "organization": "Confluent",
"timezone": "+0" },
- { "id": "ivank", "name": "Ivan Kelly", "organization": "Midokura",
"timezone": "+2" },
- { "id": "mahadev", "name": "Mahadev Konar", "organization": "Hortonworks
Inc.", "timezone": "-8" },
- { "id": "michim", "name": "Michi Mutsuzaki", "organization": "Nicira",
"timezone": "-8" },
- { "id": "cnauroth", "name": "Chris Nauroth", "organization": "Hortonworks
Inc.", "timezone": "-8" },
- { "id": "breed", "name": "Benjamin Reed", "organization": "Facebook",
"timezone": "-8" },
- { "id": "henry", "name": "Henry Robinson", "organization": "Cloudera
Inc.", "timezone": "-8" },
- { "id": "rgs", "name": "Raul Gutierrez Segales", "organization":
"Pinterest", "timezone": "-8" },
- { "id": "rakeshr", "name": "Rakesh Radhakrishnan", "organization":
"Intel", "timezone": "+5:30" },
- { "id": "hanm", "name": "Michael Han", "organization": "Twitter",
"timezone": "-8" },
- { "id": "andor", "name": "Andor Molnar", "organization": "Cloudera Inc.",
"timezone": "+1" },
- { "id": "eolivelli", "name": "Enrico Olivelli", "organization": "Diennea",
"timezone": "+1" },
- { "id": "symat", "name": "Mate Szalay-Beko", "organization": "Cloudera
Inc.", "timezone": "+1" }
- ],
- "committers": [
- { "id": "camille", "name": "Camille Fournier", "organization":
"RentTheRunway", "timezone": "-5" },
- { "id": "phunt", "name": "Patrick Hunt", "organization": "Cloudera Inc.",
"timezone": "-8" },
- { "id": "fpj", "name": "Flavio Junqueira", "organization": "Confluent",
"timezone": "+1" },
- { "id": "cnauroth", "name": "Chris Nauroth", "organization": "Hortonworks
Inc.", "timezone": "-8" },
- { "id": "mahadev", "name": "Mahadev Konar", "organization": "Hortonworks
Inc.", "timezone": "-8" },
- { "id": "gkesavan", "name": "Giridharan Kesavan", "organization":
"Hortonworks Inc.", "timezone": "-8" },
- { "id": "akornev", "name": "Andrew Kornev", "organization": "",
"timezone": "" },
- { "id": "michim", "name": "Michi Mutsuzaki", "organization": "Nicira",
"timezone": "-8" },
- { "id": "breed", "name": "Benjamin Reed", "organization": "Facebook",
"timezone": "-8" },
- { "id": "henry", "name": "Henry Robinson", "organization": "Cloudera
Inc.", "timezone": "-8" },
- { "id": "shralex", "name": "Alex Shraer", "organization": "Apple",
"timezone": "-8" },
- { "id": "thawan", "name": "Thawan Kooburat", "organization": "Facebook",
"timezone": "-8" },
- { "id": "rakeshr", "name": "Rakesh Radhakrishnan", "organization":
"Intel", "timezone": "+5:30" },
- { "id": "hdeng", "name": "Hongchao Deng", "organization": "CoreOS",
"timezone": "-8" },
- { "id": "rgs", "name": "Raul Gutierrez Segales", "organization":
"Pinterest", "timezone": "-8" },
- { "id": "hanm", "name": "Michael Han", "organization": "Twitter",
"timezone": "-8" },
- { "id": "arshad", "name": "Mohammad Arshad", "organization": "Huawei",
"timezone": "+5:30" },
- { "id": "afine", "name": "Abraham Fine", "organization": "IFTTT",
"timezone": "-8" },
- { "id": "andor", "name": "Andor Molnar", "organization": "Cloudera Inc.",
"timezone": "+1" },
- { "id": "fangmin", "name": "Fangmin Lyu", "organization": "Facebook",
"timezone": "-8" },
- { "id": "eolivelli", "name": "Enrico Olivelli", "organization": "Diennea",
"timezone": "+1" },
- { "id": "nkalmar", "name": "Norbert Kalmar", "organization": "Cloudera",
"timezone": "+1" },
- { "id": "enixon", "name": "Brian Nixon", "organization": "Facebook",
"timezone": "-8" },
- { "id": "symat", "name": "Mate Szalay-Beko", "organization": "Cloudera
Inc.", "timezone": "+1" },
- { "id": "ddiederen", "name": "Damien Diederen", "organization":
"Crosstwine Labs", "timezone": "+1" }
- ]
-}
diff --git a/zookeeper-website/app/pages/_landing/credits/index.tsx
b/zookeeper-website/app/pages/_landing/credits/index.tsx
index 50d15ee90..8ed130dc9 100644
--- a/zookeeper-website/app/pages/_landing/credits/index.tsx
+++ b/zookeeper-website/app/pages/_landing/credits/index.tsx
@@ -16,21 +16,16 @@
// limitations under the License.
//
-import data from "./developers.json";
+import developers from "./developers.json";
-interface Member {
+interface Developer {
id: string;
name: string;
- organization: string;
+ email: string;
timezone: string;
}
-interface Credits {
- pmc: Member[];
- committers: Member[];
-}
-
-function MemberTable({ members }: { members: Member[] }) {
+function DeveloperTable({ developers }: { developers: Developer[] }) {
return (
<div className="border-border my-8 w-full overflow-x-auto rounded-lg
border">
<table className="mt-0 mb-0 w-full table-fixed border-collapse text-sm">
@@ -38,22 +33,20 @@ function MemberTable({ members }: { members: Member[] }) {
<tr className="border-border border-b">
<th className="px-4 py-3 text-left font-semibold">Username</th>
<th className="px-4 py-3 text-left font-semibold">Name</th>
- <th className="px-4 py-3 text-left font-semibold">Organization</th>
+ <th className="px-4 py-3 text-left font-semibold">Email</th>
<th className="px-4 py-3 text-left font-semibold">Time Zone</th>
</tr>
</thead>
<tbody>
- {members.map((member) => (
+ {developers.map((developer) => (
<tr
- key={member.id}
+ key={developer.id}
className="border-border hover:bg-muted/50 border-b
transition-colors"
>
- <td className="px-4 py-3 align-top">{member.id}</td>
- <td className="px-4 py-3 align-top">{member.name || "-"}</td>
- <td className="px-4 py-3 align-top">
- {member.organization || "-"}
- </td>
- <td className="px-4 py-3 align-top">{member.timezone || "-"}</td>
+ <td className="px-4 py-3 align-top">{developer.id}</td>
+ <td className="px-4 py-3 align-top">{developer.name}</td>
+ <td className="px-4 py-3 align-top">{developer.email}</td>
+ <td className="px-4 py-3 align-top">{developer.timezone}</td>
</tr>
))}
</tbody>
@@ -63,8 +56,6 @@ function MemberTable({ members }: { members: Member[] }) {
}
export function CreditsPage() {
- const credits = data as Credits;
-
return (
<div className="container mx-auto px-4 py-12">
<article className="prose prose-slate dark:prose-invert max-w-none">
@@ -79,24 +70,15 @@ export function CreditsPage() {
</p>
<h2 className="mt-12 mb-4 scroll-mt-28 text-3xl font-semibold
tracking-tight md:text-4xl">
- PMC Members
- </h2>
-
- <p className="mb-4 text-base leading-7">
- ZooKeeper's active PMC members are listed below.
- </p>
-
- <MemberTable members={credits.pmc} />
-
- <h2 className="mt-12 mb-4 scroll-mt-28 text-3xl font-semibold
tracking-tight md:text-4xl">
- Committers
+ Developers
</h2>
<p className="mb-4 text-base leading-7">
- ZooKeeper's active committers are listed below.
+ ZooKeeper's developers, as listed in the project's
parent{" "}
+ <code>pom.xml</code>, are shown below.
</p>
- <MemberTable members={credits.committers} />
+ <DeveloperTable developers={developers} />
<h2 className="mt-12 mb-4 scroll-mt-28 text-3xl font-semibold
tracking-tight md:text-4xl">
Contributors
diff --git a/zookeeper-website/app/pages/_landing/home/community.tsx
b/zookeeper-website/app/pages/_landing/home/community.tsx
index 321138780..82cf9d74f 100644
--- a/zookeeper-website/app/pages/_landing/home/community.tsx
+++ b/zookeeper-website/app/pages/_landing/home/community.tsx
@@ -40,7 +40,10 @@ export function CommunitySection() {
<Link to="/mailing-lists">Mailing Lists</Link>
</Button>
<Button asChild variant="outline" size="lg">
- <Link to="https://github.com/apache/zookeeper" target="_blank">
+ <Link
+
to="https://cwiki.apache.org/confluence/display/ZOOKEEPER/HowToContribute"
+ target="_blank"
+ >
Contribute
</Link>
</Button>
@@ -64,16 +67,17 @@ export function CommunitySection() {
<ArrowUpRight className="text-muted-foreground
group-hover:text-primary absolute top-6 right-6 size-5 transition-colors" />
</Link>
</li>
- <li id="irc" className="relative p-0">
+ <li id="slack" className="relative p-0">
<Link
- to="/irc"
+ to="/slack"
className="group border-border/60 bg-card/50 hover:bg-card
hover:border-primary/50 focus-visible:ring-primary block rounded-2xl border p-6
shadow-sm transition-all duration-200 hover:shadow-md focus-visible:ring-2
focus-visible:outline-none"
>
<span className="text-foreground text-lg font-semibold">
- IRC Channel
+ Slack Channel
</span>
<p className="text-muted-foreground mt-1 text-base">
- Chat with the community on #zookeeper at irc.libera.chat.
+ Chat with the community on #zookeeper in the ASF Slack
+ workspace.
</p>
<ArrowUpRight className="text-muted-foreground
group-hover:text-primary absolute top-6 right-6 size-5 transition-colors" />
</Link>
diff --git a/zookeeper-website/app/pages/_landing/security/content.md
b/zookeeper-website/app/pages/_landing/security/content.md
index 2182c6326..66daa4a49 100644
--- a/zookeeper-website/app/pages/_landing/security/content.md
+++ b/zookeeper-website/app/pages/_landing/security/content.md
@@ -24,7 +24,7 @@ The ASF Security team maintains a page with a description of
how vulnerabilities
## Security model
-ZooKeeper is a coordination service intended for use inside a trusted network,
not exposed directly to the Internet. The [Admin
Guide](https://zookeeper.apache.org/doc/current/zookeeperAdmin.html) states it
plainly: "A ZooKeeper ensemble is expected to operate in a trusted computing
environment. It is thus recommended deploying ZooKeeper behind a firewall." A
fresh ensemble ships with no transport encryption, no peer authentication, and
world-readable/writable znodes. Hardening is a shar [...]
+ZooKeeper is a coordination service intended for use inside a trusted network,
not exposed directly to the Internet. The [Admin
Guide](https://zookeeper.apache.org/doc/current/admin-ops/administrators-guide/)
states it plainly: "A ZooKeeper ensemble is expected to operate in a trusted
computing environment. It is thus recommended deploying ZooKeeper behind a
firewall." A fresh ensemble ships with no transport encryption, no peer
authentication, and world-readable/writable znodes. Hardeni [...]
### Security is opt-in
@@ -254,7 +254,7 @@ See the documentation for more details on correct cluster
administration.
**Credit:** This issue was identified by Földi Tamás and Eugene Koontz.
-**References:**
[ZOOKEEPER-1045](https://issues.apache.org/jira/browse/ZOOKEEPER-1045) ·
[Server-Server mutual
authentication](https://cwiki.apache.org/confluence/display/ZOOKEEPER/Server-Server+mutual+authentication)
· [ZooKeeper Admin
Guide](https://zookeeper.apache.org/doc/current/zookeeperAdmin.html)
+**References:**
[ZOOKEEPER-1045](https://issues.apache.org/jira/browse/ZOOKEEPER-1045) ·
[Server-Server mutual
authentication](https://cwiki.apache.org/confluence/display/ZOOKEEPER/Server-Server+mutual+authentication)
· [ZooKeeper Admin
Guide](https://zookeeper.apache.org/doc/current/admin-ops/administrators-guide/)
---
diff --git a/zookeeper-website/app/pages/_landing/irc/content.md
b/zookeeper-website/app/pages/_landing/slack/content.md
similarity index 60%
rename from zookeeper-website/app/pages/_landing/irc/content.md
rename to zookeeper-website/app/pages/_landing/slack/content.md
index abc652be2..fd2f0bdce 100644
--- a/zookeeper-website/app/pages/_landing/irc/content.md
+++ b/zookeeper-website/app/pages/_landing/slack/content.md
@@ -16,15 +16,17 @@ See the License for the specific language governing
permissions and
limitations under the License.
-->
-# IRC Channel
+# Slack Channel
-The Apache ZooKeeper community uses IRC for real-time chat and informal
discussion.
+The Apache ZooKeeper community uses the ASF Slack workspace for real-time chat
and informal discussion.
## Channel
-**Server:** `irc.libera.chat`
+**Workspace:** [the-asf.slack.com](https://the-asf.slack.com/)
**Channel:** `#zookeeper`
-## Connecting
+## Joining
-The IRC channel can be used for online discussion about zookeeper related
stuff, but developers should be careful to transfer all the official decisions
or useful discussions to the issue tracking system.
+See the [ASF Infrastructure guide to
Slack](https://infra.apache.org/slack.html) for instructions on how to join the
workspace.
+
+The Slack channel can be used for online discussion about ZooKeeper related
topics, but developers should be careful to transfer all official decisions or
useful discussions to the issue tracking system.
diff --git a/zookeeper-website/app/pages/_landing/irc/index.tsx
b/zookeeper-website/app/pages/_landing/slack/index.tsx
similarity index 96%
copy from zookeeper-website/app/pages/_landing/irc/index.tsx
copy to zookeeper-website/app/pages/_landing/slack/index.tsx
index e20bde1ad..ee662ec46 100644
--- a/zookeeper-website/app/pages/_landing/irc/index.tsx
+++ b/zookeeper-website/app/pages/_landing/slack/index.tsx
@@ -19,6 +19,6 @@
import { MdLayout } from "@/components/mdx-components";
import Content from "./content.md";
-export function IrcPage() {
+export function SlackPage() {
return <MdLayout Content={Content} className="mt-12" />;
}
diff --git a/zookeeper-website/app/routes.ts b/zookeeper-website/app/routes.ts
index 46884abdd..3dfb62c1e 100644
--- a/zookeeper-website/app/routes.ts
+++ b/zookeeper-website/app/routes.ts
@@ -45,7 +45,7 @@ const landingRoutes =
layout("./pages/_landing/landing-layout.tsx", [
route("bylaws", "routes/_landing/bylaws.tsx"),
route("mailing-lists", "routes/_landing/mailing-lists.tsx"),
route("security", "routes/_landing/security.tsx"),
- route("irc", "routes/_landing/irc.tsx"),
+ route("slack", "routes/_landing/slack.tsx"),
route("version-control", "routes/_landing/version-control.tsx")
]);
@@ -53,8 +53,8 @@ const docRedirectRoute = route("doc",
"routes/_docs/doc-redirect.tsx");
const docsBuildRoutes = [
layout("./pages/_docs/docs-layout.tsx", [
- index("routes/_docs/docs.tsx", { id: "docs-index" }),
- route("*", "routes/_docs/docs.tsx", { id: "docs-splat" })
+ index("routes/_docs/doc.tsx", { id: "docs-index" }),
+ route("*", "routes/_docs/doc.tsx", { id: "docs-splat" })
]),
route("api/search", "routes/_api/search.ts"),
route("llms-full.txt", "routes/_api/llms-full.ts")
@@ -69,7 +69,7 @@ const devCombinedRoutes = [
landingRoutes,
docRedirectRoute,
layout("./pages/_docs/docs-layout.tsx", [
- route(`doc/r${CURRENT_VERSION}/*`, "routes/_docs/docs.tsx")
+ route(`doc/r${CURRENT_VERSION}/*`, "routes/_docs/doc.tsx")
]),
route("api/search", "routes/_api/search.ts"),
route("llms-full.txt", "routes/_api/llms-full.ts")
diff --git a/zookeeper-website/app/routes/_api/llms-full.ts
b/zookeeper-website/app/routes/_api/llms-full.ts
index e226e562f..94942e233 100644
--- a/zookeeper-website/app/routes/_api/llms-full.ts
+++ b/zookeeper-website/app/routes/_api/llms-full.ts
@@ -87,8 +87,5 @@ export function resolveLLMTextLinks(
}
function escapeRegExp(value: string): string {
- // "#" is special in regex (start of comment). Escape it in the pattern only:
- // in: /admin-ops/.../configuration-parameters#advanced-configuration
- // out: /admin-ops/.../configuration-parameters\#advanced-configuration
(RegExp string)
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
diff --git a/zookeeper-website/app/routes/_docs/doc-redirect.tsx
b/zookeeper-website/app/routes/_docs/doc-redirect.tsx
index c2cab7f9c..2ca1855b6 100644
--- a/zookeeper-website/app/routes/_docs/doc-redirect.tsx
+++ b/zookeeper-website/app/routes/_docs/doc-redirect.tsx
@@ -16,9 +16,14 @@
// limitations under the License.
//
-import { Navigate } from "react-router";
+import { redirect } from "react-router";
import { CURRENT_DOCS_PATH } from "@/lib/docs-paths";
+// /doc -> /doc/r<CURRENT_VERSION>/.
+export function loader() {
+ return redirect(`${CURRENT_DOCS_PATH}/`);
+}
+
export default function DocRedirect() {
- return <Navigate to={`${CURRENT_DOCS_PATH}/`} replace />;
+ return null;
}
diff --git a/zookeeper-website/app/routes/_docs/docs.tsx
b/zookeeper-website/app/routes/_docs/doc.tsx
similarity index 92%
rename from zookeeper-website/app/routes/_docs/docs.tsx
rename to zookeeper-website/app/routes/_docs/doc.tsx
index 2a4738456..0ff30537a 100644
--- a/zookeeper-website/app/routes/_docs/docs.tsx
+++ b/zookeeper-website/app/routes/_docs/doc.tsx
@@ -16,7 +16,7 @@
// limitations under the License.
//
-import type { Route } from "./+types/docs";
+import type { Route } from "./+types/doc";
import { source } from "@/lib/source";
import { DocsPage } from "@/pages/_docs/docs";
@@ -31,6 +31,6 @@ export async function loader({ params }: Route.LoaderArgs) {
};
}
-export default function Docs(props: Route.ComponentProps) {
+export default function Doc(props: Route.ComponentProps) {
return <DocsPage {...props} />;
}
diff --git a/zookeeper-website/app/routes/_landing/irc.tsx
b/zookeeper-website/app/routes/_landing/slack.tsx
similarity index 76%
rename from zookeeper-website/app/routes/_landing/irc.tsx
rename to zookeeper-website/app/routes/_landing/slack.tsx
index 4a2243c1e..c9130774d 100644
--- a/zookeeper-website/app/routes/_landing/irc.tsx
+++ b/zookeeper-website/app/routes/_landing/slack.tsx
@@ -16,19 +16,19 @@
// limitations under the License.
//
-import type { Route } from "./+types/irc";
-import { IrcPage } from "@/pages/_landing/irc";
+import type { Route } from "./+types/slack";
+import { SlackPage } from "@/pages/_landing/slack";
export function meta({}: Route.MetaArgs) {
return [
- { title: "IRC Channel - Apache ZooKeeper" },
+ { title: "Slack Channel - Apache ZooKeeper" },
{
name: "description",
- content: "Connect with the Apache ZooKeeper community on IRC."
+ content: "Connect with the Apache ZooKeeper community on Slack."
}
];
}
-export default function Irc() {
- return <IrcPage />;
+export default function Slack() {
+ return <SlackPage />;
}
diff --git a/zookeeper-website/e2e-tests/constants.ts
b/zookeeper-website/e2e-tests/constants.ts
index 549507c0d..cebe92f99 100644
--- a/zookeeper-website/e2e-tests/constants.ts
+++ b/zookeeper-website/e2e-tests/constants.ts
@@ -17,7 +17,6 @@
//
import { CURRENT_VERSION } from "../app/lib/current-version";
-import { RELEASED_DOC_VERSIONS } from "../app/lib/released-docs-versions";
// Current docs are served from this base in every build. Derived from
// CURRENT_VERSION so a version bump needs no e2e edits.
@@ -27,7 +26,3 @@ export const DOCS_ROOT = `/doc/r${CURRENT_VERSION}/`;
export const DOCS_URL_PATTERN = new RegExp(
`doc/r${CURRENT_VERSION.replace(/\./g, "\\.")}`
);
-
-// The no-JS older-docs fallback renders every released version from the real
-// list, so assert against its length instead of a hardcoded count.
-export const RELEASED_DOC_VERSION_COUNT = RELEASED_DOC_VERSIONS.length;
diff --git a/zookeeper-website/e2e-tests/older-docs-picker.spec.ts
b/zookeeper-website/e2e-tests/older-docs-picker.spec.ts
index c5bd2317e..2321fadb2 100644
--- a/zookeeper-website/e2e-tests/older-docs-picker.spec.ts
+++ b/zookeeper-website/e2e-tests/older-docs-picker.spec.ts
@@ -17,30 +17,16 @@
//
import { test, expect } from "@playwright/test";
-import { DOCS_ROOT, RELEASED_DOC_VERSION_COUNT } from "./constants";
+import { DOCS_ROOT } from "./constants";
+import {
+ getReleasedDocUrl,
+ getReleasedDocVersions
+} from "../app/lib/released-docs-versions";
-const MOCK_RELEASED_DOC_VERSIONS = ["3.10.0", "3.9.4", "3.9.3"];
-const RELEASED_DOC_VERSIONS_OVERRIDE_KEY =
"__released_doc_versions_override__";
-
-function expectedReleasedDocUrl(version: string): string {
- if (version === "3.9.4" || version === "3.9.3") {
- return `/doc/r${version}/index.html`;
- }
-
- return `/doc/r${version}/`;
-}
+const EXPECTED_VERSIONS = getReleasedDocVersions();
test.describe("Older Docs Picker – sidebar", () => {
test.beforeEach(async ({ page }) => {
- await page.addInitScript(
- ({ key, versions }) => {
- window.localStorage.setItem(key, JSON.stringify(versions));
- },
- {
- key: RELEASED_DOC_VERSIONS_OVERRIDE_KEY,
- versions: MOCK_RELEASED_DOC_VERSIONS
- }
- );
await page.goto(DOCS_ROOT);
await page.waitForLoadState("networkidle");
});
@@ -69,7 +55,7 @@ test.describe("Older Docs Picker – sidebar", () => {
const options = list.getByRole("option");
await expect(options.first()).toBeVisible();
- await expect(options).toHaveCount(MOCK_RELEASED_DOC_VERSIONS.length);
+ await expect(options).toHaveCount(EXPECTED_VERSIONS.length);
});
test("versions are displayed in descending order", async ({ page }) => {
@@ -79,9 +65,7 @@ test.describe("Older Docs Picker – sidebar", () => {
await expect(options.first()).toBeVisible();
const texts = await options.allTextContents();
- expect(texts.map((text) => text.trim())).toEqual(
- MOCK_RELEASED_DOC_VERSIONS
- );
+ expect(texts.map((text) => text.trim())).toEqual(EXPECTED_VERSIONS);
});
test("each version item links to the correct archive path", async ({
@@ -90,10 +74,10 @@ test.describe("Older Docs Picker – sidebar", () => {
await page.getByRole("button", { name: /older docs/i }).click();
const options = page.getByRole("option");
- await expect(options).toHaveCount(MOCK_RELEASED_DOC_VERSIONS.length);
- for (let i = 0; i < MOCK_RELEASED_DOC_VERSIONS.length; i++) {
+ await expect(options).toHaveCount(EXPECTED_VERSIONS.length);
+ for (let i = 0; i < EXPECTED_VERSIONS.length; i++) {
const href = await options.nth(i).getAttribute("href");
- expect(href).toBe(expectedReleasedDocUrl(MOCK_RELEASED_DOC_VERSIONS[i]));
+ expect(href).toBe(getReleasedDocUrl(EXPECTED_VERSIONS[i]));
}
});
@@ -108,20 +92,25 @@ test.describe("Older Docs Picker – sidebar", () => {
const allOptions = page.getByRole("option");
const totalBefore = await allOptions.count();
- // Type a prefix that matches only a subset of versions
- await input.fill("3.9");
+ const [major, minor] = EXPECTED_VERSIONS[0].split(".");
+ const prefix = `${major}.${minor}`;
+ const expectedMatches = EXPECTED_VERSIONS.filter((v) =>
+ v.startsWith(prefix)
+ ).length;
+
+ await input.fill(prefix);
await page.waitForTimeout(200);
const filtered = page.getByRole("option");
const totalAfter = await filtered.count();
- expect(totalBefore).toBe(MOCK_RELEASED_DOC_VERSIONS.length);
- expect(totalAfter).toBe(2);
+ expect(totalBefore).toBe(EXPECTED_VERSIONS.length);
+ expect(totalAfter).toBe(expectedMatches);
// Every remaining option must contain the search term
for (let i = 0; i < totalAfter; i++) {
const text = await filtered.nth(i).textContent();
- expect(text).toContain("3.9");
+ expect(text).toContain(prefix);
}
});
@@ -142,7 +131,7 @@ test.describe("Older Docs Picker – sidebar", () => {
await trigger.click();
const input = page.getByRole("combobox");
- await input.fill("3.9");
+ await input.fill("anything");
// Close the popover by pressing Escape
await page.keyboard.press("Escape");
@@ -154,110 +143,3 @@ test.describe("Older Docs Picker – sidebar", () => {
await expect(newInput).toHaveValue("");
});
});
-
-test.describe("Older Docs Picker – navbar Documentation menu", () => {
- test.beforeEach(async ({ page }) => {
- await page.addInitScript(
- ({ key, versions }) => {
- window.localStorage.setItem(key, JSON.stringify(versions));
- },
- {
- key: RELEASED_DOC_VERSIONS_OVERRIDE_KEY,
- versions: MOCK_RELEASED_DOC_VERSIONS
- }
- );
- await page.goto("/");
- await page.waitForLoadState("networkidle");
- });
-
- test("Documentation menu contains an 'Older docs' sub-menu trigger", async ({
- page
- }) => {
- // Open the Documentation dropdown (scope to banner to avoid matching the
mobile collapsible)
- await page
- .getByRole("banner")
- .getByRole("button", { name: /documentation/i })
- .click();
-
- // The sub-menu trigger should be visible
- const olderDocs = page.getByRole("menuitem", { name: /older docs/i });
- await expect(olderDocs).toBeVisible();
- });
-
- test("hovering 'Older docs' in the navbar opens a version sub-menu", async ({
- page
- }) => {
- await page
- .getByRole("banner")
- .getByRole("button", { name: /documentation/i })
- .click();
-
- const olderDocs = page.getByRole("menuitem", { name: /older docs/i });
- await olderDocs.hover();
- // ArrowRight reliably opens Radix sub-menus cross-browser (hover alone is
flaky in Firefox)
- await olderDocs.press("ArrowRight");
-
- // Wait until the sub-menu actually opens (a second menu element becomes
visible)
- await expect(page.getByRole("menu")).toHaveCount(2, { timeout: 10000 });
- const subMenu = page.getByRole("menu").last();
-
- const versionLinks = subMenu.locator('a[href^="/doc/r"]');
- await expect(versionLinks.first()).toBeVisible();
- await expect(versionLinks).toHaveCount(MOCK_RELEASED_DOC_VERSIONS.length);
- });
-
- test("navbar older-docs links point to the correct archive paths", async ({
- page
- }) => {
- await page
- .getByRole("banner")
- .getByRole("button", { name: /documentation/i })
- .click();
-
- const olderDocs = page.getByRole("menuitem", { name: /older docs/i });
- await olderDocs.hover();
- await olderDocs.press("ArrowRight");
-
- const subMenu = page.getByRole("menu").last();
- await expect(subMenu).toBeVisible();
-
- const links = subMenu.locator('a[href^="/doc/r"]');
- await expect(links).toHaveCount(MOCK_RELEASED_DOC_VERSIONS.length);
- for (let i = 0; i < MOCK_RELEASED_DOC_VERSIONS.length; i++) {
- const href = await links.nth(i).getAttribute("href");
- expect(href).toBe(expectedReleasedDocUrl(MOCK_RELEASED_DOC_VERSIONS[i]));
- }
- });
-});
-
-test.describe("Older Docs Picker – no-JS navbar fallback", () => {
- test.use({ javaScriptEnabled: false });
-
- test("Documentation menu exposes older-docs links without JavaScript", async
({
- page
- }) => {
- await page.goto("/");
-
- const documentationMenu = page
- .getByRole("banner")
- .locator("details")
- .filter({ hasText: "Documentation" })
- .first();
- await documentationMenu.locator("summary").first().click();
-
- const olderDocs = documentationMenu
- .locator("details")
- .filter({ hasText: "Older docs" })
- .first();
- await expect(olderDocs.locator("summary")).toBeVisible();
-
- await olderDocs.locator("summary").click();
-
- const links = olderDocs.locator('a[href^="/doc/r"]');
- await expect(links.first()).toBeVisible();
- await expect(links).toHaveCount(RELEASED_DOC_VERSION_COUNT);
- await expect(
- olderDocs.locator('a[href="/doc/r3.9.4/index.html"]')
- ).toBeVisible();
- });
-});
diff --git a/zookeeper-website/package-lock.json
b/zookeeper-website/package-lock.json
index 3f3b90f11..58db82f21 100644
--- a/zookeeper-website/package-lock.json
+++ b/zookeeper-website/package-lock.json
@@ -22,7 +22,7 @@
"@radix-ui/react-presence": "^1.1.5",
"@radix-ui/react-scroll-area": "1.2.2",
"@radix-ui/react-separator": "1.1.1",
- "@radix-ui/react-slot": "latest",
+ "@radix-ui/react-slot": "1.2.3",
"@radix-ui/react-tabs": "1.1.2",
"@radix-ui/react-tooltip": "1.1.6",
"@react-router/node": "^7.17.0",
diff --git a/zookeeper-website/package.json b/zookeeper-website/package.json
index 35d46131e..54367988a 100644
--- a/zookeeper-website/package.json
+++ b/zookeeper-website/package.json
@@ -3,11 +3,11 @@
"private": true,
"type": "module",
"engines": {
- "node": ">=22.12.0"
+ "node": "^22.12.0 || ^23.0.0 || ^24.0.0"
},
"scripts": {
"build": "tsx scripts/build-site.ts",
- "build:landing": "npm run fumadocs-init && ZOOKEEPER_BUILD_TARGET=landing
react-router build && npm run generate-sitemap -- --scope landing",
+ "build:landing": "npm run extract-developers &&
ZOOKEEPER_BUILD_TARGET=landing react-router build",
"build:docs": "tsx scripts/build-docs.ts",
"dev": "react-router dev",
"start": "vite preview --port 5173",
@@ -25,9 +25,9 @@
"test:e2e:debug": "playwright test --debug",
"fumadocs-init": "fumadocs-mdx",
"generate-sitemap": "tsx scripts/generate-sitemap.ts",
- "build:docs-archive": "tsx scripts/build-docs.ts",
+ "extract-developers": "node scripts/extract-developers.js",
"ci": "npm run fumadocs-init && npm run lint && npm run typecheck && npm
run test:unit:run && npm run build && npx playwright install && npm run
test:e2e",
- "ci-skip-tests": "npm run fumadocs-init && npm run build"
+ "ci-skip-tests": "npm run build"
},
"dependencies": {
"@hookform/resolvers": "^3.10.0",
@@ -46,7 +46,7 @@
"@radix-ui/react-presence": "^1.1.5",
"@radix-ui/react-scroll-area": "1.2.2",
"@radix-ui/react-separator": "1.1.1",
- "@radix-ui/react-slot": "latest",
+ "@radix-ui/react-slot": "1.2.3",
"@radix-ui/react-tabs": "1.1.2",
"@radix-ui/react-tooltip": "1.1.6",
"@react-router/node": "^7.17.0",
diff --git a/zookeeper-website/pom.xml b/zookeeper-website/pom.xml
index b0d38ce7b..568781dee 100644
--- a/zookeeper-website/pom.xml
+++ b/zookeeper-website/pom.xml
@@ -97,7 +97,7 @@
</goals>
<phase>pre-site</phase>
<configuration>
- <arguments>install</arguments>
+ <arguments>ci</arguments>
</configuration>
</execution>
<execution>
diff --git a/zookeeper-website/public/.htaccess
b/zookeeper-website/public/.htaccess
index 4b989a656..55aa05d65 100644
--- a/zookeeper-website/public/.htaccess
+++ b/zookeeper-website/public/.htaccess
@@ -36,6 +36,8 @@ ErrorDocument 404 /404.html
</IfModule>
<IfModule mod_headers.c>
+ Header always set X-Content-Type-Options "nosniff"
+ Header always set X-Frame-Options "DENY"
Header always set Cache-Control "no-cache, must-revalidate"
<FilesMatch
"\.(?:css|js|mjs|gif|ico|jpe?g|png|svg|webp|avif|woff2?|ttf|otf|eot|pdf)$">
diff --git a/zookeeper-website/public/sitemap.xml
b/zookeeper-website/public/sitemap.xml
deleted file mode 100644
index be9b4451c..000000000
--- a/zookeeper-website/public/sitemap.xml
+++ /dev/null
@@ -1 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?><urlset
xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml"
xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"><url><loc>https://zookeeper.apache.org/</loc></url><url><loc>https://zookeeper.apache.org/bylaws/</loc></url><url><loc>https://zookeeper.apache.org/credits/</loc></url
[...]
\ No newline at end of file
diff --git a/zookeeper-website/scripts/build-docs.ts
b/zookeeper-website/scripts/build-docs.ts
index b2b4d8033..3392c2da5 100644
--- a/zookeeper-website/scripts/build-docs.ts
+++ b/zookeeper-website/scripts/build-docs.ts
@@ -31,7 +31,6 @@ import { fileURLToPath } from "node:url";
import { CURRENT_VERSION } from "../app/lib/current-version";
import {
DOCS_ARCHIVE_BASE_ENV,
- DOCS_ARCHIVE_SNAPSHOT_ENV,
normalizeDocsArchiveBase
} from "../app/lib/docs-archive";
import { formatDocsBase } from "../app/lib/docs-paths";
@@ -66,19 +65,10 @@ export const ROOT_URL_PATTERNS = [
const DOCS_LOCAL_PATHS =
"(?:apidocs|assets|docs-images|fonts|images)\\b[^\"')]*|favicon\\.ico";
-interface DocsBuildArgs {
- snapshot: boolean;
-}
-
-export function parseDocsBuildArgs(
- args = process.argv.slice(2),
- scriptName = process.env.npm_lifecycle_event
-): DocsBuildArgs {
+export function parseDocsBuildArgs(args = process.argv.slice(2)): void {
if (args.length > 0) {
- throw new Error("Usage: npm run build:docs or npm run build:docs-archive");
+ throw new Error("Usage: npm run build:docs");
}
-
- return { snapshot: scriptName === "build:docs-archive" };
}
function runCommand(command: string, args: string[], env: NodeJS.ProcessEnv) {
@@ -97,13 +87,6 @@ function runCommand(command: string, args: string[], env:
NodeJS.ProcessEnv) {
}
}
-function getWebsiteCheckEnv(): NodeJS.ProcessEnv {
- const env = { ...process.env };
- delete env[DOCS_ARCHIVE_BASE_ENV];
- delete env[DOCS_ARCHIVE_SNAPSHOT_ENV];
- return env;
-}
-
async function pathExists(path: string): Promise<boolean> {
try {
await access(path);
@@ -282,21 +265,15 @@ export async function verifyArchiveOutput(
// Pure, self-contained docs build for a single version. Produces a versioned
// output at build/doc/r<version>/ with its own assets, .htaccess, rewritten
-// docs-local URLs, and llms-full.txt. Pass `snapshot: true` for archived
-// versions so the "switch to latest" banner shows.
-export async function buildDocs(
- version: string,
- { snapshot = false }: { snapshot?: boolean } = {}
-): Promise<string> {
+// docs-local URLs, and llms-full.txt.
+export async function buildDocs(version: string): Promise<string> {
const docsBase = normalizeDocsArchiveBase(formatDocsBase(version));
const env = {
...process.env,
- [DOCS_ARCHIVE_BASE_ENV]: docsBase,
- ...(snapshot ? { [DOCS_ARCHIVE_SNAPSHOT_ENV]: "1" } : {})
+ [DOCS_ARCHIVE_BASE_ENV]: docsBase
};
console.log(`Building docs for ${docsBase}`);
- runCommand("npm", ["run", "fumadocs-init"], env);
runCommand("npx", ["react-router", "build"], env);
const outputDir = await copyDocsOutput(version);
@@ -308,36 +285,14 @@ export async function buildDocs(
}
export async function main() {
- const { snapshot } = parseDocsBuildArgs();
+ parseDocsBuildArgs();
- if (snapshot) {
- console.log("Running website checks before building the docs archive");
- runCommand("npm", ["run", "ci"], getWebsiteCheckEnv());
- }
-
- const version = CURRENT_VERSION;
- await buildDocs(version, { snapshot });
-
- // The current docs build owns the docs slice of the sitemap. Archive
snapshots
- // are intentionally excluded, so they never touch it.
- if (!snapshot) {
- runCommand(
- "npm",
- ["run", "generate-sitemap", "--", "--scope", "docs"],
- process.env
- );
- }
+ await buildDocs(CURRENT_VERSION);
- if (snapshot) {
- console.log("");
- console.log("Publish with:");
- console.log(` git checkout asf-site`);
- console.log(` rm -rf content/doc/r${version}`);
- console.log(` mkdir -p content/doc/r${version}`);
- console.log(
- ` cp -R zookeeper-website/build/doc/r${version}/.
content/doc/r${version}/`
- );
- }
+ // react-router build emits to build/client/, then buildDocs() assembles the
+ // self-contained deliverable at build/doc/r<v>/. The leftover build/client/
+ // is just intermediate output — drop it so build/doc/ is the only root.
+ await rm(BUILD_CLIENT_DIR, { recursive: true, force: true });
}
if (process.argv[1] === fileURLToPath(import.meta.url)) {
diff --git a/zookeeper-website/scripts/build-site.ts
b/zookeeper-website/scripts/build-site.ts
index 28b6b2b07..f046c3cb3 100644
--- a/zookeeper-website/scripts/build-site.ts
+++ b/zookeeper-website/scripts/build-site.ts
@@ -60,6 +60,9 @@ export async function main() {
await rm(stashDir, { recursive: true, force: true });
await cp(docsOutputDir, stashDir, { recursive: true });
+ console.log("Extracting developers from parent pom.xml");
+ runCommand("npm", ["run", "extract-developers"], process.env);
+
console.log("Building landing site");
runCommand("npx", ["react-router", "build"], {
...process.env,
diff --git a/zookeeper-website/app/pages/_landing/irc/index.tsx
b/zookeeper-website/scripts/extract-developers.d.ts
similarity index 75%
rename from zookeeper-website/app/pages/_landing/irc/index.tsx
rename to zookeeper-website/scripts/extract-developers.d.ts
index e20bde1ad..bd4d7e475 100644
--- a/zookeeper-website/app/pages/_landing/irc/index.tsx
+++ b/zookeeper-website/scripts/extract-developers.d.ts
@@ -16,9 +16,13 @@
// limitations under the License.
//
-import { MdLayout } from "@/components/mdx-components";
-import Content from "./content.md";
-
-export function IrcPage() {
- return <MdLayout Content={Content} className="mt-12" />;
+export interface Developer {
+ id: string;
+ name: string;
+ email: string;
+ timezone: string;
}
+
+export function extractField(block: string, fieldName: string): string;
+export function parseDevelopers(pomContent: string): Developer[];
+export function main(): void;
diff --git a/zookeeper-website/scripts/extract-developers.js
b/zookeeper-website/scripts/extract-developers.js
new file mode 100644
index 000000000..c3a683557
--- /dev/null
+++ b/zookeeper-website/scripts/extract-developers.js
@@ -0,0 +1,113 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+import fs from 'fs';
+import path from 'path';
+import { fileURLToPath } from 'url';
+
+/**
+ * Extracts a field value from a developer XML block
+ * @param {string} block - The XML block containing developer information
+ * @param {string} fieldName - The name of the field to extract
+ * @returns {string} The field value or '-' if not present
+ */
+export function extractField(block, fieldName) {
+ const fieldRegex = new RegExp(`<${fieldName}>(.*?)<\/${fieldName}>`, 's');
+ const fieldMatch = block.match(fieldRegex);
+ return fieldMatch ? fieldMatch[1].trim() : '-';
+}
+
+/**
+ * Parses developers from POM XML content
+ * @param {string} pomContent - The content of the pom.xml file
+ * @returns {Array<Object>} Array of developer objects
+ * @throws {Error} If no developers section is found
+ */
+export function parseDevelopers(pomContent) {
+ // Extract developers using regex
+ const developersMatch =
pomContent.match(/<developers>([\s\S]*?)<\/developers>/);
+ if (!developersMatch) {
+ throw new Error('No developers section found in pom.xml');
+ }
+
+ const developersXml = developersMatch[1];
+ // Match each developer block
+ const developerBlockRegex = /<developer>([\s\S]*?)<\/developer>/gs;
+
+ const developers = [];
+ let match;
+
+ while ((match = developerBlockRegex.exec(developersXml)) !== null) {
+ const block = match[1];
+
+ const id = extractField(block, 'id');
+ const name = extractField(block, 'name');
+ const email = extractField(block, 'email');
+ const timezone = extractField(block, 'timezone');
+
+ developers.push({
+ id,
+ name,
+ email,
+ timezone,
+ });
+ }
+
+ return developers;
+}
+
+/**
+ * Main function to extract developers and write to JSON file
+ */
+export function main() {
+ const __filename = fileURLToPath(import.meta.url);
+ const __dirname = path.dirname(__filename);
+
+ // Read the parent pom.xml
+ const pomPath = path.join(__dirname, '..', '..', 'pom.xml');
+ const pomContent = fs.readFileSync(pomPath, 'utf-8');
+
+ let developers;
+ try {
+ developers = parseDevelopers(pomContent);
+ } catch (error) {
+ console.error(error.message);
+ process.exit(1);
+ }
+
+ console.log(`Extracted ${developers.length} developers from pom.xml`);
+
+ // Write to JSON file
+ const outputPath = path.join(
+ __dirname,
+ '..',
+ 'app',
+ 'pages',
+ '_landing',
+ 'credits',
+ 'developers.json',
+ );
+ fs.writeFileSync(outputPath, JSON.stringify(developers, null, 2));
+
+ console.log(`Developers data written to ${outputPath}`);
+}
+
+// Run main if this file is executed directly
+if (import.meta.url === `file://${process.argv[1]}`) {
+ main();
+}
diff --git a/zookeeper-website/scripts/generate-sitemap.ts
b/zookeeper-website/scripts/generate-sitemap.ts
index e67efc45f..fc02747e1 100644
--- a/zookeeper-website/scripts/generate-sitemap.ts
+++ b/zookeeper-website/scripts/generate-sitemap.ts
@@ -45,9 +45,9 @@ const EXCLUDED_HTML_PATHS = new Set([
"404.html",
"404/index.html",
"__spa-fallback.html",
- // The /doc redirect is a client-side <Navigate> with no redirect marker in
its
- // prerendered HTML, so it must be excluded explicitly (and it only exists in
- // builds that include landing, which would otherwise diverge from docs
builds).
+ // /doc is a loader-driven redirect to /doc/r<CURRENT_VERSION>/. Its meta-
+ // refresh marker would already be caught by REDIRECT_PAGE_PATTERNS, but we
+ // exclude it explicitly so landing-inclusive and docs-only builds agree.
"doc/index.html"
]);
diff --git a/zookeeper-website/unit-tests/archive-docs-banner.test.tsx
b/zookeeper-website/unit-tests/archive-docs-banner.test.tsx
deleted file mode 100644
index fb8d3c01e..000000000
--- a/zookeeper-website/unit-tests/archive-docs-banner.test.tsx
+++ /dev/null
@@ -1,82 +0,0 @@
-//
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements. See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership. The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License. You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
-import { render, screen } from "@testing-library/react";
-import userEvent from "@testing-library/user-event";
-import { ArchiveDocsBanner } from "@/components/docs/archive-docs-banner";
-import { CURRENT_VERSION } from "@/lib/current-version";
-import { DOCS_ARCHIVE_SNAPSHOT_ENV } from "@/lib/docs-archive";
-
-const storageKey =
`zookeeper-docs-archive-banner-dismissed:${CURRENT_VERSION}`;
-
-function enableArchiveBuild() {
- vi.stubEnv(DOCS_ARCHIVE_SNAPSHOT_ENV, "1");
-}
-
-beforeEach(() => {
- localStorage.clear();
-});
-
-afterEach(() => {
- vi.unstubAllEnvs();
- localStorage.clear();
-});
-
-describe("ArchiveDocsBanner", () => {
- it("renders nothing outside an archive build", () => {
- render(<ArchiveDocsBanner />);
-
- expect(screen.queryByRole("status")).not.toBeInTheDocument();
- });
-
- it("shows the switch-to-latest notice on an archive snapshot", () => {
- enableArchiveBuild();
- render(<ArchiveDocsBanner />);
-
- expect(
- screen.getByText(new RegExp(`viewing the v${CURRENT_VERSION} docs`, "i"))
- ).toBeInTheDocument();
- expect(
- screen.getByRole("link", { name: /switch to latest/i })
- ).toHaveAttribute("href", "/doc/");
- });
-
- it("dismisses the banner and remembers the dismissal", async () => {
- const user = userEvent.setup();
- enableArchiveBuild();
- render(<ArchiveDocsBanner />);
-
- expect(screen.getByRole("status")).toBeInTheDocument();
-
- await user.click(
- screen.getByRole("button", { name: /dismiss archived docs notice/i })
- );
-
- expect(screen.queryByRole("status")).not.toBeInTheDocument();
- expect(localStorage.getItem(storageKey)).toBe("1");
- });
-
- it("stays hidden when already dismissed", () => {
- enableArchiveBuild();
- localStorage.setItem(storageKey, "1");
- render(<ArchiveDocsBanner />);
-
- expect(screen.queryByRole("status")).not.toBeInTheDocument();
- });
-});
diff --git a/zookeeper-website/unit-tests/build-docs-archive.test.ts
b/zookeeper-website/unit-tests/build-docs.test.ts
similarity index 96%
rename from zookeeper-website/unit-tests/build-docs-archive.test.ts
rename to zookeeper-website/unit-tests/build-docs.test.ts
index aa4fb9066..cb2341af2 100644
--- a/zookeeper-website/unit-tests/build-docs-archive.test.ts
+++ b/zookeeper-website/unit-tests/build-docs.test.ts
@@ -74,14 +74,8 @@ afterEach(async () => {
});
describe("parseDocsBuildArgs", () => {
- it("uses the current version docs build by default", () => {
- expect(parseDocsBuildArgs([])).toEqual({ snapshot: false });
- });
-
- it("marks archive builds from the npm script name", () => {
- expect(parseDocsBuildArgs([], "build:docs-archive")).toEqual({
- snapshot: true
- });
+ it("accepts no arguments", () => {
+ expect(() => parseDocsBuildArgs([])).not.toThrow();
});
it("rejects CLI version overrides", () => {
diff --git a/zookeeper-website/unit-tests/extract-developers.test.ts
b/zookeeper-website/unit-tests/extract-developers.test.ts
new file mode 100644
index 000000000..ea55dbfe3
--- /dev/null
+++ b/zookeeper-website/unit-tests/extract-developers.test.ts
@@ -0,0 +1,255 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+import { describe, it, expect } from "vitest";
+import {
+ extractField,
+ parseDevelopers
+} from "../scripts/extract-developers.js";
+
+describe("extract-developers script", () => {
+ const createPomXml = (
+ developersXml: string
+ ) => `<?xml version="1.0" encoding="UTF-8"?>
+<project>
+ <developers>
+${developersXml}
+ </developers>
+</project>`;
+
+ describe("extractField", () => {
+ it("should extract existing field value", () => {
+ const block = "<id>johndoe</id><name>John Doe</name>";
+ expect(extractField(block, "id")).toBe("johndoe");
+ expect(extractField(block, "name")).toBe("John Doe");
+ });
+
+ it('should return "-" for missing field', () => {
+ const block = "<id>johndoe</id><email>[email protected]</email>";
+ expect(extractField(block, "name")).toBe("-");
+ expect(extractField(block, "timezone")).toBe("-");
+ });
+
+ it("should trim whitespace from field values", () => {
+ const block = "<id> johndoe </id><name> John Doe </name>";
+ expect(extractField(block, "id")).toBe("johndoe");
+ expect(extractField(block, "name")).toBe("John Doe");
+ });
+
+ it("should handle multiline field values", () => {
+ const block = "<name>John\nDoe</name>";
+ const result = extractField(block, "name");
+ expect(result).toContain("John");
+ expect(result).toContain("Doe");
+ });
+ });
+
+ describe("parseDevelopers", () => {
+ it("should extract developers with all fields present", () => {
+ const pomXml = createPomXml(` <developer>
+ <id>johndoe</id>
+ <name>John Doe</name>
+ <email>[email protected]</email>
+ <timezone>-8</timezone>
+ </developer>
+ <developer>
+ <id>janedoe</id>
+ <name>Jane Doe</name>
+ <email>[email protected]</email>
+ <timezone>+1</timezone>
+ </developer>`);
+
+ const developers = parseDevelopers(pomXml);
+
+ expect(developers).toHaveLength(2);
+ expect(developers[0]).toEqual({
+ id: "johndoe",
+ name: "John Doe",
+ email: "[email protected]",
+ timezone: "-8"
+ });
+ expect(developers[1]).toEqual({
+ id: "janedoe",
+ name: "Jane Doe",
+ email: "[email protected]",
+ timezone: "+1"
+ });
+ });
+
+ it("should handle missing name field", () => {
+ const pomXml = createPomXml(` <developer>
+ <id>tianjy</id>
+ <email>[email protected]</email>
+ <timezone>+8</timezone>
+ </developer>`);
+
+ const developers = parseDevelopers(pomXml);
+
+ expect(developers).toHaveLength(1);
+ expect(developers[0]).toEqual({
+ id: "tianjy",
+ name: "-",
+ email: "[email protected]",
+ timezone: "+8"
+ });
+ });
+
+ it("should handle missing email field", () => {
+ const pomXml = createPomXml(` <developer>
+ <id>testuser</id>
+ <name>Test User</name>
+ <timezone>+0</timezone>
+ </developer>`);
+
+ const developers = parseDevelopers(pomXml);
+
+ expect(developers).toHaveLength(1);
+ expect(developers[0]).toEqual({
+ id: "testuser",
+ name: "Test User",
+ email: "-",
+ timezone: "+0"
+ });
+ });
+
+ it("should handle multiple missing fields", () => {
+ const pomXml = createPomXml(` <developer>
+ <id>minimaluser</id>
+ <email>[email protected]</email>
+ </developer>`);
+
+ const developers = parseDevelopers(pomXml);
+
+ expect(developers).toHaveLength(1);
+ expect(developers[0]).toEqual({
+ id: "minimaluser",
+ name: "-",
+ email: "[email protected]",
+ timezone: "-"
+ });
+ });
+
+ it("should handle mixed complete and incomplete developer entries", () => {
+ const pomXml = createPomXml(` <developer>
+ <id>complete</id>
+ <name>Complete User</name>
+ <email>[email protected]</email>
+ <timezone>+1</timezone>
+ </developer>
+ <developer>
+ <id>incomplete</id>
+ <email>[email protected]</email>
+ <timezone>+2</timezone>
+ </developer>
+ <developer>
+ <id>another</id>
+ <name>Another User</name>
+ <email>[email protected]</email>
+ <timezone>-5</timezone>
+ </developer>`);
+
+ const developers = parseDevelopers(pomXml);
+
+ expect(developers).toHaveLength(3);
+ expect(developers[0]).toEqual({
+ id: "complete",
+ name: "Complete User",
+ email: "[email protected]",
+ timezone: "+1"
+ });
+ expect(developers[1]).toEqual({
+ id: "incomplete",
+ name: "-",
+ email: "[email protected]",
+ timezone: "+2"
+ });
+ expect(developers[2]).toEqual({
+ id: "another",
+ name: "Another User",
+ email: "[email protected]",
+ timezone: "-5"
+ });
+ });
+
+ it("should trim whitespace from field values", () => {
+ const pomXml = createPomXml(` <developer>
+ <id> spacey </id>
+ <name> Spacey User </name>
+ <email> [email protected] </email>
+ <timezone> +3 </timezone>
+ </developer>`);
+
+ const developers = parseDevelopers(pomXml);
+
+ expect(developers).toHaveLength(1);
+ expect(developers[0]).toEqual({
+ id: "spacey",
+ name: "Spacey User",
+ email: "[email protected]",
+ timezone: "+3"
+ });
+ });
+
+ it("should handle fields in different order", () => {
+ const pomXml = createPomXml(` <developer>
+ <timezone>+5</timezone>
+ <name>Reordered User</name>
+ <id>reordered</id>
+ <email>[email protected]</email>
+ </developer>`);
+
+ const developers = parseDevelopers(pomXml);
+
+ expect(developers).toHaveLength(1);
+ expect(developers[0]).toEqual({
+ id: "reordered",
+ name: "Reordered User",
+ email: "[email protected]",
+ timezone: "+5"
+ });
+ });
+
+ it("should handle empty developers section", () => {
+ const pomXml = createPomXml("");
+
+ const developers = parseDevelopers(pomXml);
+ expect(developers).toHaveLength(0);
+ });
+
+ it("should throw when developers section is missing", () => {
+ expect(() => parseDevelopers("<project></project>")).toThrow(
+ /No developers section found/
+ );
+ });
+
+ it("should handle special characters in field values", () => {
+ const pomXml = createPomXml(` <developer>
+ <id>special&user</id>
+ <name>User <Name></name>
+ <email>[email protected]</email>
+ <timezone>+0</timezone>
+ </developer>`);
+
+ const developers = parseDevelopers(pomXml);
+
+ expect(developers).toHaveLength(1);
+ expect(developers[0].id).toBe("special&user");
+ expect(developers[0].name).toBe("User <Name>");
+ });
+ });
+});
diff --git a/zookeeper-website/unit-tests/llms-full.test.ts
b/zookeeper-website/unit-tests/llms-full.test.ts
index e4684dcdd..68cdc6a4a 100644
--- a/zookeeper-website/unit-tests/llms-full.test.ts
+++ b/zookeeper-website/unit-tests/llms-full.test.ts
@@ -86,6 +86,16 @@ describe("resolveLLMTextLinks", () => {
).toBe(text);
});
+ it("rewrites links with hash fragments in the path", () => {
+ const href =
+
"/admin-ops/administrators-guide/configuration-parameters#advanced-configuration";
+ const text = `[Advanced Configuration](${href})`;
+
+ expect(resolveLLMTextLinks(text, [{ href }])).toBe(
+ `[Advanced Configuration](${CURRENT_DOCS_PATH}${href})`
+ );
+ });
+
it("rewrites links inside MDX JSX text blocks when Fumadocs extracted them",
() => {
const text = `<Callout>
See [Dynamic Reconfiguration](/admin-ops/dynamic-reconfiguration).
diff --git a/zookeeper-website/unit-tests/older-docs-picker.test.tsx
b/zookeeper-website/unit-tests/older-docs-picker.test.tsx
index 1baad0e13..5738c0612 100644
--- a/zookeeper-website/unit-tests/older-docs-picker.test.tsx
+++ b/zookeeper-website/unit-tests/older-docs-picker.test.tsx
@@ -23,9 +23,8 @@ import { renderWithProviders } from "./utils";
import { OlderDocsPicker } from "@/components/docs/older-docs-picker";
import {
getReleasedDocUrl,
- LEGACY_RELEASED_DOC_VERSIONS,
+ RAW_RELEASED_DOC_VERSIONS,
RELEASED_DOC_VERSIONS,
- REACT_ROUTER_RELEASED_DOC_VERSIONS,
sortVersionsDesc
} from "@/lib/released-docs-versions";
@@ -73,22 +72,16 @@ describe("RELEASED_DOC_VERSIONS", () => {
expect(RELEASED_DOC_VERSIONS).toEqual(sorted);
});
- it("combines legacy and React Router archive versions", () => {
+ it("matches the sorted raw archive versions", () => {
expect(RELEASED_DOC_VERSIONS).toEqual(
- sortVersionsDesc([
- ...LEGACY_RELEASED_DOC_VERSIONS,
- ...REACT_ROUTER_RELEASED_DOC_VERSIONS
- ])
+ sortVersionsDesc([...RAW_RELEASED_DOC_VERSIONS])
);
});
});
describe("getReleasedDocUrl", () => {
- it("uses /index.html for legacy static archives", () => {
- expect(getReleasedDocUrl("3.9.4")).toBe("/doc/r3.9.4/index.html");
- });
-
- it("uses /doc/r<version>/ for React Router archives", () => {
+ it("links every archived version to /doc/r<version>/", () => {
+ expect(getReleasedDocUrl("3.9.4")).toBe("/doc/r3.9.4/");
expect(getReleasedDocUrl("3.10.0")).toBe("/doc/r3.10.0/");
});
});
diff --git a/zookeeper-website/unit-tests/released-docs-versions.test.ts
b/zookeeper-website/unit-tests/released-docs-versions.test.ts
index 4b10e123b..0d6c858e7 100644
--- a/zookeeper-website/unit-tests/released-docs-versions.test.ts
+++ b/zookeeper-website/unit-tests/released-docs-versions.test.ts
@@ -19,9 +19,7 @@
import { describe, it, expect } from "vitest";
import {
getReleasedDocUrl,
- LEGACY_RELEASED_DOC_VERSIONS,
RAW_RELEASED_DOC_VERSIONS,
- REACT_ROUTER_RELEASED_DOC_VERSIONS,
RELEASED_DOC_VERSIONS,
sortVersionsDesc
} from "@/lib/released-docs-versions";
@@ -51,8 +49,8 @@ describe("sortVersionsDesc with mocked released-docs
versions", () => {
});
describe("RAW_RELEASED_DOC_VERSIONS", () => {
- it("exposes an array", () => {
- expect(Array.isArray(RAW_RELEASED_DOC_VERSIONS)).toBe(true);
+ it("exposes a Set", () => {
+ expect(RAW_RELEASED_DOC_VERSIONS).toBeInstanceOf(Set);
});
it("strips the leading 'r' prefix from every folder name", () => {
@@ -74,52 +72,28 @@ describe("RAW_RELEASED_DOC_VERSIONS", () => {
});
it("contains the first and latest archived documentation versions", () => {
- expect(RAW_RELEASED_DOC_VERSIONS).toContain("3.1.2");
- expect(RAW_RELEASED_DOC_VERSIONS).toContain("3.9.4");
- });
-
- it("contains every archived docs version exactly once", () => {
- expect(new Set(RAW_RELEASED_DOC_VERSIONS).size).toBe(
- RAW_RELEASED_DOC_VERSIONS.length
- );
- expect(RAW_RELEASED_DOC_VERSIONS.length).toBe(52);
- });
-
- it("combines legacy and React Router archive versions", () => {
- expect(RAW_RELEASED_DOC_VERSIONS).toEqual([
- ...LEGACY_RELEASED_DOC_VERSIONS,
- ...REACT_ROUTER_RELEASED_DOC_VERSIONS
- ]);
- });
-
- it("keeps legacy and React Router archive version sets distinct", () => {
- const legacyVersions = new Set(LEGACY_RELEASED_DOC_VERSIONS);
- REACT_ROUTER_RELEASED_DOC_VERSIONS.forEach((version) => {
- expect(legacyVersions.has(version)).toBe(false);
- });
+ expect(RAW_RELEASED_DOC_VERSIONS.has("3.1.2")).toBe(true);
+ expect(RAW_RELEASED_DOC_VERSIONS.has("3.9.4")).toBe(true);
});
});
describe("RELEASED_DOC_VERSIONS (sorted output)", () => {
it("equals sortVersionsDesc applied to the raw virtual-module data", () => {
expect(RELEASED_DOC_VERSIONS).toEqual(
- sortVersionsDesc(RAW_RELEASED_DOC_VERSIONS)
+ sortVersionsDesc([...RAW_RELEASED_DOC_VERSIONS])
);
});
it("contains the same entries as RAW_RELEASED_DOC_VERSIONS, just reordered",
() => {
expect(RELEASED_DOC_VERSIONS.slice().sort()).toEqual(
- RAW_RELEASED_DOC_VERSIONS.slice().sort()
+ [...RAW_RELEASED_DOC_VERSIONS].sort()
);
});
});
describe("getReleasedDocUrl", () => {
- it("links legacy static archives to index.html", () => {
- expect(getReleasedDocUrl("3.9.4")).toBe("/doc/r3.9.4/index.html");
- });
-
- it("links new React Router archives to /doc/r<version>/", () => {
+ it("links every archived version to /doc/r<version>/", () => {
+ expect(getReleasedDocUrl("3.9.4")).toBe("/doc/r3.9.4/");
expect(getReleasedDocUrl("3.10.0")).toBe("/doc/r3.10.0/");
});
});