diff --git a/hbase-website/.gitignore b/hbase-website/.gitignore index 00909066a6fa..76974dfd2872 100644 --- a/hbase-website/.gitignore +++ b/hbase-website/.gitignore @@ -39,6 +39,7 @@ lerna-debug.log* /app/pages/_docs/docs/_mdx/(multi-page)/configuration/hbase-default.md /app/lib/export-pdf/hbase-version.json /public/books/** +/public/sitemap.xml # Playwright node_modules/ diff --git a/hbase-website/README.md b/hbase-website/README.md index a67a85699757..020bccef96fa 100644 --- a/hbase-website/README.md +++ b/hbase-website/README.md @@ -476,16 +476,18 @@ When you run `mvn site`, the website module automatically: - `npm run extract-hbase-config` - Extract data from `hbase-default.xml` to `app/pages/_docs/docs/_mdx/(multi-page)/configuration/hbase-default.md` - `npm run extract-hbase-version` - Extract version from root `pom.xml` to `app/lib/export-pdf/hbase-version.json` - `npm run test:unit:run` - Vitest unit tests - - `npm run test:e2e` - Playwright e2e tests - `npm run build` - Production build + - `npm run generate-sitemap` - Generates `public/sitemap.xml` and `build/client/sitemap.xml` + - `npm run test:e2e` - Playwright e2e tests `npm run ci-skip-tests` executes: - `npm run extract-developers` - Extract developers from parent pom.xml - `npm run extract-hbase-config` - Extract data from `hbase-default.xml` to `app/pages/_docs/docs/_mdx/(multi-page)/configuration/hbase-default.md` - `npm run extract-hbase-version` - Extract version from root `pom.xml` to `app/lib/export-pdf/hbase-version.json` + - `npm run build` - Production build + - `npm run generate-sitemap` - Generates `public/sitemap.xml` and `build/client/sitemap.xml` - `npx playwright install` - Installs Playwright browsers - `npm run export-pdf` - Generates docs PDF assets through Playwright - - `npm run build` - Production build 6. **Build Output**: Generated files are in `build/` directory diff --git a/hbase-website/app/routes.ts b/hbase-website/app/routes.ts index e66051c7f16d..18311ce3aea9 100644 --- a/hbase-website/app/routes.ts +++ b/hbase-website/app/routes.ts @@ -32,7 +32,8 @@ export default [ route("source-repository", "routes/_landing/source-repository.tsx"), route("acid-semantics", "routes/_landing/acid-semantics.tsx"), route("news", "routes/_landing/news.tsx"), - route("export-control", "routes/_landing/export-control.tsx") + route("export-control", "routes/_landing/export-control.tsx"), + route("404", "routes/404.tsx") ]), // Docs layout("./pages/_docs/docs-layout.tsx", [route("docs/*", "routes/_docs/docs.tsx")]), diff --git a/hbase-website/app/routes/404.tsx b/hbase-website/app/routes/404.tsx new file mode 100644 index 000000000000..159b7f8518fb --- /dev/null +++ b/hbase-website/app/routes/404.tsx @@ -0,0 +1,57 @@ +// +// 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 type { Route } from "./+types/404"; +import { Link } from "@/components/link"; +import { Button } from "@/ui/button"; + +export function meta({}: Route.MetaArgs) { + return [ + { title: "Page Not Found - Apache HBase" }, + { + name: "description", + content: + "The page you are looking for does not exist. The route you were trying to visit might have changed." + }, + { name: "robots", content: "noindex, nofollow" } + ]; +} + +export default function NotFound() { + return ( +
+
+

+ 404 +

+

+ Page not found +

+

+ The page you are looking for doesn't exist. The route you were trying to visit might + have changed. +

+
+ +
+
+
+ ); +} diff --git a/hbase-website/package-lock.json b/hbase-website/package-lock.json index 79b3260fa8de..045eae690034 100644 --- a/hbase-website/package-lock.json +++ b/hbase-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": "*", + "@radix-ui/react-slot": "latest", "@radix-ui/react-tabs": "1.1.2", "@radix-ui/react-tooltip": "1.1.6", "@react-router/node": "^7.12.0", @@ -84,6 +84,7 @@ "prettier": "^3.6.2", "prettier-plugin-tailwindcss": "^0.6.14", "serve": "^14.2.6", + "sitemap": "^9.0.1", "tailwindcss": "^4.1.13", "tsx": "^4.21.0", "tw-animate-css": "1.3.3", @@ -3573,6 +3574,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3586,6 +3588,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3599,6 +3602,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3612,6 +3616,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3625,6 +3630,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3638,6 +3644,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3651,6 +3658,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3664,6 +3672,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3677,6 +3686,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3690,6 +3700,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3703,6 +3714,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3716,6 +3728,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3729,6 +3742,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3742,6 +3756,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3755,6 +3770,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3768,6 +3784,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3781,6 +3798,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3794,6 +3812,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3807,6 +3826,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3820,6 +3840,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3833,6 +3854,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3846,6 +3868,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3859,6 +3882,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3872,6 +3896,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3885,6 +3910,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4582,7 +4608,7 @@ "version": "22.18.11", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.11.tgz", "integrity": "sha512-Gd33J2XIrXurb+eT2ktze3rJAfAp9ZNjlBdh4SVgyrKEOADwCbdUDaK7QgJno8Ue4kcajscsKqu6n8OBG3hhCQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -4607,6 +4633,16 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/sax": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", + "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", @@ -6650,7 +6686,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -7849,6 +7885,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -8855,7 +8892,7 @@ "version": "4.12.0", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.12.0.tgz", "integrity": "sha512-LScr2aNr2FbjAjZh2C6X6BxRx1/x+aTDExct/xyq2XKbYOiG5c0aK7pMsSuyc0brz3ibr/lbQiHD9jzt4lccJw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "resolve-pkg-maps": "^1.0.0" @@ -10116,7 +10153,7 @@ "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "devOptional": true, + "dev": true, "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -10292,7 +10329,7 @@ "version": "1.30.1", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", - "devOptional": true, + "dev": true, "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -10324,6 +10361,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -10344,6 +10382,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -10363,6 +10402,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "freebsd" @@ -10382,6 +10422,7 @@ "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "linux" @@ -10401,6 +10442,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -10420,6 +10462,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -10439,6 +10482,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -10458,6 +10502,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -10477,6 +10522,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -10496,6 +10542,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -13361,7 +13408,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "devOptional": true, + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" @@ -13499,6 +13546,16 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -13910,6 +13967,43 @@ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true }, + "node_modules/sitemap": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-9.0.1.tgz", + "integrity": "sha512-S6hzjGJSG3d6if0YoF5kTyeRJvia6FSTBroE5fQ0bu1QNxyJqhhinfUsXi9fH3MgtXODWvwo2BDyQSnhPQ88uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^24.9.2", + "@types/sax": "^1.2.1", + "arg": "^5.0.0", + "sax": "^1.4.1" + }, + "bin": { + "sitemap": "dist/esm/cli.js" + }, + "engines": { + "node": ">=20.19.5", + "npm": ">=10.8.2" + } + }, + "node_modules/sitemap/node_modules/@types/node": { + "version": "24.12.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz", + "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/sitemap/node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -14445,7 +14539,7 @@ "version": "4.21.0", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "esbuild": "~0.27.0", @@ -14468,6 +14562,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14484,6 +14579,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14500,6 +14596,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14516,6 +14613,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14532,6 +14630,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14548,6 +14647,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14564,6 +14664,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14580,6 +14681,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14596,6 +14698,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14612,6 +14715,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14628,6 +14732,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14644,6 +14749,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14660,6 +14766,7 @@ "cpu": [ "mips64el" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14676,6 +14783,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14692,6 +14800,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14708,6 +14817,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14724,6 +14834,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14740,6 +14851,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14756,6 +14868,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14772,6 +14885,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14788,6 +14902,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14804,6 +14919,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14820,6 +14936,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14836,6 +14953,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14852,6 +14970,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14868,6 +14987,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -14881,7 +15001,7 @@ "version": "0.27.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", - "devOptional": true, + "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -15107,7 +15227,7 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/unified": { @@ -15589,6 +15709,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15605,6 +15726,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15621,6 +15743,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15637,6 +15760,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15653,6 +15777,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15669,6 +15794,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15685,6 +15811,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15701,6 +15828,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15717,6 +15845,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15733,6 +15862,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15749,6 +15879,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15765,6 +15896,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15781,6 +15913,7 @@ "cpu": [ "mips64el" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15797,6 +15930,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15813,6 +15947,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15829,6 +15964,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15845,6 +15981,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15861,6 +15998,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15877,6 +16015,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15893,6 +16032,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15909,6 +16049,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15925,6 +16066,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15941,6 +16083,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15957,6 +16100,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15973,6 +16117,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -15989,6 +16134,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ diff --git a/hbase-website/package.json b/hbase-website/package.json index 09fbbb4f10b9..b2bcb7d5b4ca 100644 --- a/hbase-website/package.json +++ b/hbase-website/package.json @@ -25,11 +25,12 @@ "extract-hbase-config": "node scripts/extract-hbase-config.js", "extract-hbase-version": "node scripts/extract-hbase-version.js", "extract-all": "node scripts/extract-developers.js && node scripts/extract-hbase-config.js && node scripts/extract-hbase-version.js", + "generate-sitemap": "tsx scripts/generate-sitemap.ts", "export-pdf": "playwright test e2e-tests/export-pdf.spec.ts --project=chromium --workers=1", "fumadocs-init": "fumadocs-mdx", "copy-pdf-to-build": "cp -r public/books build/client", - "ci": "npm run extract-all && 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 && npm run copy-pdf-to-build", - "ci-skip-tests": "npm run extract-all && npm run fumadocs-init && npm run build && npx playwright install && npm run export-pdf && npm run copy-pdf-to-build" + "ci": "npm run extract-all && npm run fumadocs-init && npm run lint && npm run typecheck && npm run test:unit:run && npm run build && npm run generate-sitemap && npx playwright install && npm run test:e2e && npm run copy-pdf-to-build", + "ci-skip-tests": "npm run extract-all && npm run fumadocs-init && npm run build && npm run generate-sitemap && npx playwright install && npm run export-pdf && npm run copy-pdf-to-build" }, "dependencies": { "@hookform/resolvers": "^3.10.0", @@ -110,6 +111,7 @@ "prettier": "^3.6.2", "prettier-plugin-tailwindcss": "^0.6.14", "serve": "^14.2.6", + "sitemap": "^9.0.1", "tailwindcss": "^4.1.13", "tsx": "^4.21.0", "tw-animate-css": "1.3.3", diff --git a/hbase-website/public/robots.txt b/hbase-website/public/robots.txt new file mode 100644 index 000000000000..45e9d4886142 --- /dev/null +++ b/hbase-website/public/robots.txt @@ -0,0 +1,5 @@ +User-agent: * +Allow: / +Disallow: /404 + +Sitemap: https://hbase.apache.org/sitemap.xml diff --git a/hbase-website/scripts/generate-sitemap.ts b/hbase-website/scripts/generate-sitemap.ts new file mode 100644 index 000000000000..5feb3b89c102 --- /dev/null +++ b/hbase-website/scripts/generate-sitemap.ts @@ -0,0 +1,116 @@ +// +// 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 { access, glob, readFile, writeFile } from "node:fs/promises"; +import { join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { SitemapStream, streamToPromise } from "sitemap"; + +const ROOT = join(import.meta.dirname, ".."); +const PUBLIC_DIR = join(ROOT, "public"); +const BUILD_DIR = join(ROOT, "build", "client"); +const PUBLIC_SITEMAP_PATH = join(PUBLIC_DIR, "sitemap.xml"); +const BUILD_SITEMAP_PATH = join(BUILD_DIR, "sitemap.xml"); +const SITE_URL = "https://hbase.apache.org"; + +const EXCLUDED_HTML_PATHS = new Set(["404/index.html", "__spa-fallback.html"]); + +const REDIRECT_PAGE_PATTERNS = [ + /window\.location\.replace\(/i, + /http-equiv=["']refresh/i, + />Redirecting to .*?If it does not happen automatically,/is +]; + +interface SitemapEntry { + url: string; +} + +export function normalizePath(relativePath: string): string { + return relativePath.replaceAll("\\", "/"); +} + +export function toSiteUrl(relativePath: string): string { + if (relativePath === "index.html") { + return "/"; + } + + if (relativePath.endsWith("/index.html")) { + return `/${relativePath.slice(0, -"/index.html".length)}/`; + } + + return `/${relativePath}`; +} + +export function isRedirectOnlyPage(html: string): boolean { + return REDIRECT_PAGE_PATTERNS.some((pattern) => pattern.test(html)); +} + +export function shouldIncludeInSitemap(relativePath: string, html: string): boolean { + if (EXCLUDED_HTML_PATHS.has(relativePath)) { + return false; + } + + return !isRedirectOnlyPage(html); +} + +export async function collectSitemapEntries(): Promise { + const entries: SitemapEntry[] = []; + + for await (const htmlPath of glob("**/*.html", { cwd: BUILD_DIR })) { + const relativePath = normalizePath(htmlPath); + const filePath = join(BUILD_DIR, relativePath); + const html = await readFile(filePath, "utf8"); + + if (!shouldIncludeInSitemap(relativePath, html)) { + continue; + } + + entries.push({ + url: toSiteUrl(relativePath) + }); + } + + entries.sort((left, right) => left.url.localeCompare(right.url)); + return entries; +} + +export async function main() { + await access(BUILD_DIR); + + const entries = await collectSitemapEntries(); + const sitemap = new SitemapStream({ hostname: SITE_URL }); + + for (const entry of entries) { + sitemap.write(entry); + } + + sitemap.end(); + + const xml = `${(await streamToPromise(sitemap)).toString().trimEnd()}\n`; + await Promise.all([writeFile(PUBLIC_SITEMAP_PATH, xml), writeFile(BUILD_SITEMAP_PATH, xml)]); + + console.log( + `Generated sitemap with ${entries.length} URLs at ${PUBLIC_SITEMAP_PATH} and ${BUILD_SITEMAP_PATH}` + ); +} + +const isDirectRun = process.argv[1] === fileURLToPath(import.meta.url); + +if (isDirectRun) { + await main(); +} diff --git a/hbase-website/unit-tests/generate-sitemap.test.ts b/hbase-website/unit-tests/generate-sitemap.test.ts new file mode 100644 index 000000000000..dd773e52436c --- /dev/null +++ b/hbase-website/unit-tests/generate-sitemap.test.ts @@ -0,0 +1,90 @@ +// +// 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, expect, it } from "vitest"; +import { + isRedirectOnlyPage, + normalizePath, + shouldIncludeInSitemap, + toSiteUrl +} from "../scripts/generate-sitemap"; + +describe("normalizePath", () => { + it("preserves forward-slash paths", () => { + expect(normalizePath("docs/index.html")).toBe("docs/index.html"); + }); + + it("converts windows separators to forward slashes", () => { + expect(normalizePath("docs\\configuration\\index.html")).toBe("docs/configuration/index.html"); + }); +}); + +describe("toSiteUrl", () => { + it("maps the root index file to slash", () => { + expect(toSiteUrl("index.html")).toBe("/"); + }); + + it("maps nested index files to trailing-slash URLs", () => { + expect(toSiteUrl("docs/configuration/index.html")).toBe("/docs/configuration/"); + }); + + it("preserves non-index html filenames", () => { + expect(toSiteUrl("llms-full.txt.html")).toBe("/llms-full.txt.html"); + }); +}); + +describe("isRedirectOnlyPage", () => { + it("detects javascript redirect pages", () => { + expect(isRedirectOnlyPage('')).toBe(true); + }); + + it("detects meta refresh redirects", () => { + expect(isRedirectOnlyPage('')).toBe( + true + ); + }); + + it("does not flag ordinary html pages", () => { + expect(isRedirectOnlyPage("

Apache HBase

")).toBe(false); + }); +}); + +describe("shouldIncludeInSitemap", () => { + it("excludes explicitly ignored html paths", () => { + expect(shouldIncludeInSitemap("404/index.html", "")).toBe(false); + expect(shouldIncludeInSitemap("__spa-fallback.html", "")).toBe(false); + }); + + it("excludes redirect-only pages even if the path is not prelisted", () => { + expect( + shouldIncludeInSitemap( + "docs/redirect/index.html", + '' + ) + ).toBe(false); + }); + + it("includes normal prerendered pages", () => { + expect( + shouldIncludeInSitemap( + "docs/configuration/index.html", + "

Configuration

" + ) + ).toBe(true); + }); +}); diff --git a/pom.xml b/pom.xml index 92e2fab10f97..ace848e6361a 100644 --- a/pom.xml +++ b/pom.xml @@ -2140,6 +2140,8 @@ **/public/old-book-static-files/hbase.css **/public/old-book-static-files/font-awesome.css + + **/public/robots.txt **/*.vm