This is an automated email from the ASF dual-hosted git repository.

lhotari pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/pulsar-site.git


The following commit(s) were added to refs/heads/main by this push:
     new e25dc4bac21 Replace scripts/replace.js with a Docusaurus markdown 
preprocessor
e25dc4bac21 is described below

commit e25dc4bac21d7f5d4826e672f5ae7d3bcbf95ac2
Author: Lari Hotari <[email protected]>
AuthorDate: Wed Apr 22 15:33:13 2026 +0300

    Replace scripts/replace.js with a Docusaurus markdown preprocessor
    
    Move the @pulsar:...@ token expansion and the {@inject:...} link expansion
    out of a standalone post-processing script / inline config block into two
    focused preprocessor modules under src/server/markdownPreprocessors/. The
    new pulsarVariables preprocessor scopes itself by filePath to docs/ and
    versioned_docs/version-*/, preserving the exact scope of the old script
    while making replacements visible during `yarn start` (previously only
    applied in CI). Computed version/URL helpers are centralized in
    src/config/pulsarVariables.ts so React components can consume them
    directly without needing a separate replacement pass. Drops the
    replace-in-file dependency and the node invocation from site_builder.py.
    Also adds @pulsar:version:adapters@.
    
    BUILD_ALL_VERSION=1
---
 README.md                                          |  15 ++
 docs/reference-configuration.md                    |   2 +-
 docusaurus.config.ts                               | 109 +-------
 package.json                                       |   1 -
 scripts/replace.js                                 | 291 ---------------------
 src/config/pulsarVariables.ts                      | 192 ++++++++++++++
 src/server/markdownPreprocessors/inject.ts         |  84 ++++++
 .../markdownPreprocessors/pulsarVariables.ts       |  24 ++
 tools/pytools/lib/execute/site_builder.py          |   2 -
 .../version-3.0.x/reference-configuration.md       |   2 +-
 .../version-4.0.x/reference-configuration.md       |   2 +-
 .../version-4.2.x/reference-configuration.md       |   2 +-
 yarn.lock                                          |  82 +-----
 13 files changed, 333 insertions(+), 475 deletions(-)

diff --git a/README.md b/README.md
index 2267704ceac..454d680c325 100644
--- a/README.md
+++ b/README.md
@@ -33,6 +33,21 @@ After committing the changes for the `docs` directory, you 
can use the `docs-too
 ./scripts/docs-tool.sh apply_changes_to_versioned_docs
 ```
 
+## Markdown placeholders
+
+Markdown files under `docs/` and `versioned_docs/version-*/` are run through
+two preprocessors at build time:
+
+- `@pulsar:version@`, `@pulsar:rpm:client@`, `@pulsar:apidoc:python@`, etc. —
+  version-aware tokens. The values come from `versions.json`, 
`site-baseurls.js`,
+  and `data/release-*.js`. See `src/config/pulsarVariables.ts` for the full 
list.
+- `{@inject:javadoc:Name:org/...}`, `{@inject:endpoint|GET|/admin/...}` — link
+  expansion for Javadoc, GitHub, REST endpoints. See
+  `src/server/markdownPreprocessors/inject.ts`.
+
+To reference the same values from React components, import from
+`@site/src/config/pulsarVariables` — no placeholder is needed in `.js`/`.tsx`.
+
 ## More information
 
 * [Pulsar Website contribution 
guide](https://pulsar.apache.org/contribute/site-intro/)
diff --git a/docs/reference-configuration.md b/docs/reference-configuration.md
index e60d9b6348a..653a28e5b92 100644
--- a/docs/reference-configuration.md
+++ b/docs/reference-configuration.md
@@ -4,4 +4,4 @@ title: Pulsar Configuration
 sidebar_label: "Pulsar Configuration"
 ---
 
-For a complete list of Pulsar configuration, visit the [Pulsar 
Reference](pathname:///reference/#/@pulsar:version_reference@/) website.
+For a complete list of Pulsar configuration, visit the <a 
href="pathname:///reference/#/@pulsar:version_reference@/" target="_blank" 
rel="noopener noreferrer">Pulsar Reference</a> website.
diff --git a/docusaurus.config.ts b/docusaurus.config.ts
index b9b5592fb5e..0e67aa81706 100644
--- a/docusaurus.config.ts
+++ b/docusaurus.config.ts
@@ -28,99 +28,15 @@ try {
 }
 
 const {
-  siteUrl,
-  javadocUrl,
-  restApiUrl,
-  functionsApiUrl,
-  sourceApiUrl,
-  sinkApiUrl,
-  packagesApiUrl,
-  transactionsApiUrl,
-  lookupApiUrl,
-  githubUrl,
   githubSiteUrl,
   baseUrl,
-  restApiBaseUrlMapping
+  restApiBaseUrlMapping,
 } = require("./site-baseurls");
 
-const injectLinkParse = (prefix, name, path) => {
-  if (prefix == "javadoc") {
-    return {
-      link: javadocUrl + path,
-      text: name,
-    };
-  } else if (prefix == "github") {
-    return {
-      link: githubUrl + "/tree/master/" + path,
-      text: name,
-    };
-  } else if (prefix == "rest") {
-    return {
-      link: restApiUrl + "#" + path,
-      text: name,
-    };
-  } else if (prefix == "functions") {
-    return {
-      link: functionsApiUrl + "#" + path,
-      text: name,
-    };
-  } else if (prefix == "source") {
-    return {
-      link: sourceApiUrl + "#" + path,
-      text: name,
-    };
-  } else if (prefix == "sink") {
-    return {
-      link: sinkApiUrl + "#" + path,
-      text: name,
-    };
-  } else if (prefix == "packages") {
-    return {
-      link: packagesApiUrl + "#" + path,
-      text: name,
-    };
-  }
-
-  return {
-    link: path,
-    text: name,
-  };
-};
-
-const injectLinkParseForEndpoint = (info) => {
-  let [method, path, suffix] = info.split("|");
-
-  if (!suffix) {
-    suffix = "";
-  }
-
-  let restPath = path.split("/");
-  const restApiVersion = restPath[2];
-  const restApiType = restPath[3];
-  const restBaseUrl = restApiBaseUrlMapping[restApiType] || restApiUrl;
-
-  let restUrl;
-  if (suffix.indexOf("?version=") >= 0) {
-    const suffixAndVersion = suffix.split("?version=")
-    restUrl = "version=" + suffixAndVersion[1] + "&apiversion=" + 
restApiVersion + "#" + suffixAndVersion[0];
-    if (suffixAndVersion[0].startsWith("operation/")) {
-      path += suffixAndVersion[0].slice("operation".length)
-    }
-  } else {
-    restUrl = "version=master&apiversion=" + restApiVersion + "#" + suffix;
-    if (suffix.startsWith("operation/")) {
-      path += suffix.slice("operation".length)
-    }
-  }
-
-  return {
-    text: method + " " + path,
-    link: restBaseUrl + "?" + restUrl,
-  };
-};
-
 /** @type {import('@docusaurus/types').Config} */
 module.exports = async function createConfigAsync() {
+  const injectPreprocessor = (await 
import("./src/server/markdownPreprocessors/inject")).default;
+  const pulsarVariablesPreprocessor = (await 
import("./src/server/markdownPreprocessors/pulsarVariables")).default;
   return {
     future: {
       faster: {
@@ -147,21 +63,10 @@ module.exports = async function createConfigAsync() {
       hooks: {
         onBrokenMarkdownLinks: "warn",
       },
-      preprocessor: ({ filePath, fileContent }) => {
-        return fileContent.replaceAll(/{@inject:([^}]+)}/g, (_, p1) => {
-          const p1Trimmed = p1.trim();
-          const endpointPrefix = 'endpoint|';
-          let link, text;
-          if (p1Trimmed.startsWith(endpointPrefix)) {
-            // @ts-ignore
-            ({ link, text } = 
injectLinkParseForEndpoint(p1Trimmed.substring(endpointPrefix.length)));
-          } else {
-            const [prefix, name, path] = p1Trimmed.split(':');
-            ({ link, text } = injectLinkParse(prefix, name, path));
-          }
-          return `[${text}](${link})`; // Format as a markdown link
-        });
-      },
+      preprocessor: (args) => injectPreprocessor({
+        ...args,
+        fileContent: pulsarVariablesPreprocessor(args),
+      }),
     },
     themes: ['@docusaurus/theme-mermaid'],
     themeConfig:
diff --git a/package.json b/package.json
index 06dc7dfc0ed..1fb9b729929 100644
--- a/package.json
+++ b/package.json
@@ -60,7 +60,6 @@
     "react-svg": "^16.1.34",
     "rehype-katex": "^7.0.1",
     "remark-math": "^6.0.0",
-    "replace-in-file": "6.3.2",
     "search-insights": "^2.17.3",
     "semver": "^7.7.4",
     "sine-waves": "^0.3.0",
diff --git a/scripts/replace.js b/scripts/replace.js
deleted file mode 100644
index 191c041ab92..00000000000
--- a/scripts/replace.js
+++ /dev/null
@@ -1,291 +0,0 @@
-const replace = require("replace-in-file");
-
-const semver = require("semver");
-const CWD = process.cwd();
-const urlConfig = require("../site-baseurls.js");
-const nextDocsDir = `${CWD}/docs`;
-const docsDir = `${CWD}/versioned_docs`;
-const restApiVersions = require("../static/swagger/restApiVersions.json");
-const compareVersions = require("compare-versions");
-
-function getRealVersion(version) {
-  let versionMap = {};
-  let _vsGroups = {};
-  for (let [key, val] of Object.entries(restApiVersions)) {
-    if (key == "master" || compareVersions.compare(key, "2.8.0", "<")) {
-      versionMap[key] = key;
-    } else {
-      let [one, two] = key.split(".");
-      let _tKey = one + "." + two + ".x";
-      _vsGroups[_tKey] = [...(_vsGroups[_tKey] || []), key];
-    }
-  }
-  for (let [key, val] of Object.entries(_vsGroups)) {
-    let _tKey = val.sort((a, b) => {
-      return -compareVersions.compare(b, a, "<");
-    })[0];
-    versionMap[key] = _tKey;
-  }
-  return versionMap[version] || version;
-}
-
-function downloadPageUrl() {
-  return `${urlConfig.baseUrl}download`;
-}
-
-function binaryReleaseUrl(version) {
-  return 
`https://archive.apache.org/dist/pulsar/pulsar-${version}/apache-pulsar-${version}-bin.tar.gz`;
-}
-
-function connectorReleaseUrl(version) {
-  let v = semver.coerce(version);
-  if (v.compareMain("2.3.0") >= 0) {
-    return 
`https://archive.apache.org/dist/pulsar/pulsar-${version}/connectors`;
-  } else {
-    return 
`https://archive.apache.org/dist/pulsar/pulsar-${version}/apache-pulsar-io-connectors-${version}-bin.tar.gz`;
-  }
-}
-
-function offloaderReleaseUrl(version) {
-  return 
`https://archive.apache.org/dist/pulsar/pulsar-${version}/apache-pulsar-offloaders-${version}-bin.tar.gz`;
-}
-
-function prestoPulsarReleaseUrl(version) {
-  return 
`https://archive.apache.org/dist/pulsar/pulsar-${version}/pulsar-presto-connector-${version}.tar.gz`;
-}
-
-function rpmDistUrl(version, type) {
-  let v = semver.coerce(version);
-  if (v.compareMain("2.11.0") < 0) {
-    let resolvedVersion = version;
-    if (v.minor === 8) {
-      resolvedVersion = "2.8.4";
-    } else if (v.minor === 9) {
-      resolvedVersion = "2.9.4";
-    } else if (v.minor === 10) {
-      resolvedVersion = "2.10.2"
-    }
-    return 
`https://archive.apache.org/dist/pulsar/pulsar-${resolvedVersion}/RPMS/apache-pulsar-client${type}-${resolvedVersion}-1.x86_64.rpm`;
-  } else {
-    const versions = require(`${CWD}/data/release-cpp`);
-    const ver = versions[0].tagName.substring(1);
-    return 
`https://archive.apache.org/dist/pulsar/pulsar-client-cpp-${ver}/rpm-x86_64/x86_64/apache-pulsar-client${type}-${ver}-1.x86_64.rpm`
-  }
-}
-
-function debDistUrl(version, type) {
-  let v = semver.coerce(version);
-  if (v.compareMain("2.11.0") < 0) {
-    let resolvedVersion = version;
-    if (v.minor === 8) {
-      resolvedVersion = "2.8.4";
-    } else if (v.minor === 9) {
-      resolvedVersion = "2.9.4";
-    } else if (v.minor === 10) {
-      resolvedVersion = "2.10.2"
-    }
-    return 
`https://archive.apache.org/dist/pulsar/pulsar-${resolvedVersion}/DEB/apache-pulsar-client${type}.deb`;
-  } else {
-    const versions = require(`${CWD}/data/release-cpp`);
-    const ver = versions[0].tagName.substring(1);
-    return 
`https://archive.apache.org/dist/pulsar/pulsar-client-cpp-${ver}/deb-x86_64/apache-pulsar-client${type}.deb`
-  }
-}
-
-function clientPythonVersion(version) {
-  if (version === "2.6.4") {
-    return "2.6.3";
-  }
-  let v = semver.coerce(version);
-  if (v.compareMain("2.8.0") < 0) {
-    return version;
-  }
-  if (v.compareMain("2.11.0") < 0) {
-    if (v.minor === 8) {
-      return "2.8.4";
-    } else if (v.minor === 9) {
-      return "2.9.4";
-    } else if (v.minor === 10) {
-      return "2.10.2"
-    }
-  }
-  let versions = require(`${CWD}/data/release-python`);
-  return `${versions[0].tagName.substring(1)}`;
-}
-
-function clientPythonVersionUrl(version) {
-  if (version === "2.6.4") {
-    return `${urlConfig.siteUrl}/api/python/2.6.3`;
-  }
-  let v = semver.coerce(version);
-  if (v.compareMain("2.8.0") < 0) {
-    return `${urlConfig.siteUrl}/api/python/${version}`;
-  }
-  if (v.compareMain("2.11.0") < 0) {
-    return `${urlConfig.siteUrl}/api/python/${v.major}.${v.minor}.x`;
-  }
-  let versions = require(`${CWD}/data/release-python`);
-  return `${urlConfig.siteUrl}/api/python/${versions[0].vtag}`;
-}
-
-function clientCPPVersionUrl(version) {
-  let v = semver.coerce(version);
-  if (v.compareMain("2.8.0") < 0) {
-    return `${urlConfig.siteUrl}/api/cpp/${version}`;
-  }
-  if (v.compareMain("2.11.0") < 0) {
-    return `${urlConfig.siteUrl}/api/cpp/${v.major}.${v.minor}.x`;
-  }
-  let versions = require(`${CWD}/data/release-cpp`);
-  return `${urlConfig.siteUrl}/api/cpp/${versions[0].vtag}`;
-}
-
-function javadocVersionUrl(version, type) {
-  return `(${urlConfig.siteUrl}/api/${type}/${version}`
-}
-
-function referenceVersion(version) {
-  let v = semver.coerce(version);
-  if (v.compareMain("2.7.0") < 0) {
-    return "2.6.x";
-  }
-  return `${v.major}.${v.minor}.x`;
-}
-
-function doReplace(options) {
-  replace(options)
-    .then((changes) => {
-      if (options.dry) {
-        console.log("Modified files:");
-        console.log(changes.join("\n"));
-      }
-    })
-    .catch((error) => {
-      console.error("Error occurred:", error);
-    });
-}
-
-const versions = JSON.parse(require("fs").readFileSync(`${CWD}/versions.json`, 
"utf8"));
-const latestMajorRelease = versions[0];
-const latestVersion = getRealVersion(latestMajorRelease);
-
-const from = [
-  /@pulsar:version_number@/g,
-  /@pulsar:version@/g,
-  /@pulsar:version_origin@/g,
-  /@pulsar:version_reference@/g,
-  /pulsar:binary_release_url/g,
-  /pulsar:connector_release_url/g,
-  /pulsar:offloader_release_url/g,
-  /pulsar:presto_pulsar_connector_release_url/g,
-  /pulsar:download_page_url/g,
-  /@pulsar:rpm:client@/g,
-  /@pulsar:rpm:client-debuginfo@/g,
-  /@pulsar:rpm:client-devel@/g,
-  /@pulsar:deb:client@/g,
-  /@pulsar:deb:client-devel@/g,
-
-  /@pulsar:dist_rpm:client@/g,
-  /@pulsar:dist_rpm:client-debuginfo@/g,
-  /@pulsar:dist_rpm:client-devel@/g,
-  /@pulsar:dist_deb:client@/g,
-  /@pulsar:dist_deb:client-devel@/g,
-
-  /@pulsar:version:python@/g,
-
-  /@pulsar:apidoc:python@/g,
-  /@pulsar:apidoc:cpp@/g,
-  /\(\/api\/pulsar-functions/g,
-  /\(\/api\/client/g,
-  /\(\/api\/admin/g,
-
-  /@pulsar:version_number@/g,
-
-  /\[([^\]]*)\]\((\/tools\/pulsar[^\)]*)\)/g,
-];
-
-const options = {
-  files: [`${nextDocsDir}/*.md`, `${nextDocsDir}/**/*.md`],
-  from: from,
-  to: [
-    `${latestVersion}`,
-    `${latestVersion}`,
-    `${latestVersion}`,
-    `next`,
-    binaryReleaseUrl(`${latestVersion}`),
-    connectorReleaseUrl(`${latestVersion}`),
-    offloaderReleaseUrl(`${latestVersion}`),
-    prestoPulsarReleaseUrl(`${latestVersion}`),
-    downloadPageUrl(),
-    rpmDistUrl(`${latestVersion}`, ""),
-    rpmDistUrl(`${latestVersion}`, "-debuginfo"),
-    rpmDistUrl(`${latestVersion}`, "-devel"),
-    debDistUrl(`${latestVersion}`, ""),
-    debDistUrl(`${latestVersion}`, "-dev"),
-
-    rpmDistUrl(`${latestVersion}`, ""),
-    rpmDistUrl(`${latestVersion}`, "-debuginfo"),
-    rpmDistUrl(`${latestVersion}`, "-devel"),
-    debDistUrl(`${latestVersion}`, ""),
-    debDistUrl(`${latestVersion}`, "-dev"),
-
-    clientPythonVersion(`${latestMajorRelease}`),
-
-    clientPythonVersionUrl(`${latestMajorRelease}`),
-    clientCPPVersionUrl(`${latestMajorRelease}`),
-    javadocVersionUrl(`${latestMajorRelease}`, "pulsar-functions"),
-    javadocVersionUrl(`${latestMajorRelease}`, "client"),
-    javadocVersionUrl(`${latestMajorRelease}`, "admin"),
-
-    `${latestVersion}`,
-
-    '<a href="$2" target="_blank">$1</a>',
-  ],
-  dry: false,
-};
-
-doReplace(options);
-
-// replaces versions
-for (let _v of versions) {
-  const v = getRealVersion(_v)
-  const vWithoutIncubating = v.replace("-incubating", "");
-  const opts = {
-    files: [
-      `${docsDir}/version-${_v}/*.md`,
-      `${docsDir}/version-${_v}/**/*.md`,
-    ],
-    from: from,
-    to: [
-      `${vWithoutIncubating}`,
-      `${v}`,
-      `${_v}`,
-      referenceVersion(v),
-      binaryReleaseUrl(`${v}`),
-      connectorReleaseUrl(`${v}`),
-      offloaderReleaseUrl(`${v}`),
-      prestoPulsarReleaseUrl(`${v}`),
-      downloadPageUrl(),
-      rpmDistUrl(`${v}`, ""),
-      rpmDistUrl(`${v}`, "-debuginfo"),
-      rpmDistUrl(`${v}`, "-devel"),
-      debDistUrl(`${v}`, ""),
-      debDistUrl(`${v}`, "-dev"),
-      rpmDistUrl(`${v}`, ""),
-      rpmDistUrl(`${v}`, "-debuginfo"),
-      rpmDistUrl(`${v}`, "-devel"),
-      debDistUrl(`${v}`, ""),
-      debDistUrl(`${v}`, "-dev"),
-      clientPythonVersion(`${v}`),
-      clientPythonVersionUrl(`${v}`),
-      clientCPPVersionUrl(`${v}`),
-      javadocVersionUrl(`${_v}`, "pulsar-functions"),
-      javadocVersionUrl(`${_v}`, "client"),
-      javadocVersionUrl(`${_v}`, "admin"),
-      `${v}`,
-      '<a href="$2" target="_blank">$1</a>',
-    ],
-    dry: false,
-  };
-  doReplace(opts);
-}
diff --git a/src/config/pulsarVariables.ts b/src/config/pulsarVariables.ts
new file mode 100644
index 00000000000..da6b7d35743
--- /dev/null
+++ b/src/config/pulsarVariables.ts
@@ -0,0 +1,192 @@
+import semver from "semver";
+import * as compareVersions from "compare-versions";
+
+import versionsList from "../../versions.json";
+import restApiVersions from "../../static/swagger/restApiVersions.json";
+
+// CommonJS modules without type declarations — typed inline below.
+// eslint-disable-next-line @typescript-eslint/no-var-requires
+const urlConfig: {
+  siteUrl: string;
+  baseUrl: string;
+} = require("../../site-baseurls");
+
+type ClientRelease = {tagName: string; vtag: string};
+// eslint-disable-next-line @typescript-eslint/no-var-requires
+const releaseCpp: ClientRelease[] = require("../../data/release-cpp");
+// eslint-disable-next-line @typescript-eslint/no-var-requires
+const releasePython: ClientRelease[] = require("../../data/release-python");
+// eslint-disable-next-line @typescript-eslint/no-var-requires
+const releasePulsarAdapters: string[] = 
require("../../data/release-pulsar-adapters");
+
+export const pulsarAdaptersVersion: string = releasePulsarAdapters[0];
+
+const versions = versionsList as string[];
+
+export const latestMajorRelease: string = versions[0];
+
+export function getRealVersion(version: string): string {
+  const versionMap: Record<string, string> = {};
+  const vsGroups: Record<string, string[]> = {};
+  for (const key of Object.keys(restApiVersions as Record<string, unknown>)) {
+    if (key === "master" || compareVersions.compare(key, "2.8.0", "<")) {
+      versionMap[key] = key;
+    } else {
+      const [major, minor] = key.split(".");
+      const groupKey = `${major}.${minor}.x`;
+      vsGroups[groupKey] = [...(vsGroups[groupKey] || []), key];
+    }
+  }
+  for (const [groupKey, members] of Object.entries(vsGroups)) {
+    const latest = members.sort((a, b) => -compareVersions.compare(b, a, 
"<"))[0];
+    versionMap[groupKey] = latest;
+  }
+  return versionMap[version] || version;
+}
+
+export const latestVersion: string = getRealVersion(latestMajorRelease);
+
+export function downloadPageUrl(): string {
+  return `${urlConfig.baseUrl}download`;
+}
+
+export function binaryReleaseUrl(version: string): string {
+  return 
`https://archive.apache.org/dist/pulsar/pulsar-${version}/apache-pulsar-${version}-bin.tar.gz`;
+}
+
+export function connectorReleaseUrl(version: string): string {
+  const v = semver.coerce(version)!;
+  if (v.compareMain("2.3.0") >= 0) {
+    return 
`https://archive.apache.org/dist/pulsar/pulsar-${version}/connectors`;
+  }
+  return 
`https://archive.apache.org/dist/pulsar/pulsar-${version}/apache-pulsar-io-connectors-${version}-bin.tar.gz`;
+}
+
+export function offloaderReleaseUrl(version: string): string {
+  return 
`https://archive.apache.org/dist/pulsar/pulsar-${version}/apache-pulsar-offloaders-${version}-bin.tar.gz`;
+}
+
+export function prestoPulsarReleaseUrl(version: string): string {
+  return 
`https://archive.apache.org/dist/pulsar/pulsar-${version}/pulsar-presto-connector-${version}.tar.gz`;
+}
+
+// For Pulsar 2.8–2.10 the client binaries are republished under a fixed
+// patch version; newer majors use the pulsar-client-cpp release line instead.
+function legacyDistVersion(version: string): string | null {
+  const v = semver.coerce(version)!;
+  if (v.compareMain("2.11.0") >= 0) return null;
+  if (v.minor === 8) return "2.8.4";
+  if (v.minor === 9) return "2.9.4";
+  if (v.minor === 10) return "2.10.2";
+  return version;
+}
+
+export function rpmDistUrl(version: string, type: string): string {
+  const legacy = legacyDistVersion(version);
+  if (legacy !== null) {
+    return 
`https://archive.apache.org/dist/pulsar/pulsar-${legacy}/RPMS/apache-pulsar-client${type}-${legacy}-1.x86_64.rpm`;
+  }
+  const ver = releaseCpp[0].tagName.substring(1);
+  return 
`https://archive.apache.org/dist/pulsar/pulsar-client-cpp-${ver}/rpm-x86_64/x86_64/apache-pulsar-client${type}-${ver}-1.x86_64.rpm`;
+}
+
+export function debDistUrl(version: string, type: string): string {
+  const legacy = legacyDistVersion(version);
+  if (legacy !== null) {
+    return 
`https://archive.apache.org/dist/pulsar/pulsar-${legacy}/DEB/apache-pulsar-client${type}.deb`;
+  }
+  const ver = releaseCpp[0].tagName.substring(1);
+  return 
`https://archive.apache.org/dist/pulsar/pulsar-client-cpp-${ver}/deb-x86_64/apache-pulsar-client${type}.deb`;
+}
+
+export function clientPythonVersion(version: string): string {
+  if (version === "2.6.4") return "2.6.3";
+  const v = semver.coerce(version)!;
+  if (v.compareMain("2.8.0") < 0) return version;
+  if (v.compareMain("2.11.0") < 0) {
+    if (v.minor === 8) return "2.8.4";
+    if (v.minor === 9) return "2.9.4";
+    if (v.minor === 10) return "2.10.2";
+  }
+  return releasePython[0].tagName.substring(1);
+}
+
+export function clientPythonVersionUrl(version: string): string {
+  if (version === "2.6.4") return `${urlConfig.siteUrl}/api/python/2.6.3`;
+  const v = semver.coerce(version)!;
+  if (v.compareMain("2.8.0") < 0) return 
`${urlConfig.siteUrl}/api/python/${version}`;
+  if (v.compareMain("2.11.0") < 0) return 
`${urlConfig.siteUrl}/api/python/${v.major}.${v.minor}.x`;
+  return `${urlConfig.siteUrl}/api/python/${releasePython[0].vtag}`;
+}
+
+export function clientCPPVersionUrl(version: string): string {
+  const v = semver.coerce(version)!;
+  if (v.compareMain("2.8.0") < 0) return 
`${urlConfig.siteUrl}/api/cpp/${version}`;
+  if (v.compareMain("2.11.0") < 0) return 
`${urlConfig.siteUrl}/api/cpp/${v.major}.${v.minor}.x`;
+  return `${urlConfig.siteUrl}/api/cpp/${releaseCpp[0].vtag}`;
+}
+
+// The leading `(` is intentional: paired regexes match the `(` head of a
+// markdown link and this replacement preserves it.
+export function javadocVersionUrl(version: string, type: string): string {
+  return `(${urlConfig.siteUrl}/api/${type}/${version}`;
+}
+
+export function referenceVersion(version: string): string {
+  const v = semver.coerce(version)!;
+  if (v.compareMain("2.7.0") < 0) return "2.6.x";
+  return `${v.major}.${v.minor}.x`;
+}
+
+export type Replacement = [RegExp, string];
+
+/**
+ * Ordered [regex, replacement] pairs for a given version key.
+ * `"current"` → next/current docs; any other string is an origin version
+ * from versions.json (e.g. "3.0.x", "2.10.x", "2.6.4").
+ */
+export function resolveTokens(versionKey: string): Replacement[] {
+  const isCurrent = versionKey === "current";
+  const originVersion = isCurrent ? latestMajorRelease : versionKey;
+  const resolvedVersion = getRealVersion(originVersion);
+
+  // Quirks preserved for behavioral compatibility:
+  //   - current docs: version_origin = resolvedVersion (not origin)
+  //   - versioned docs: version_number strips any -incubating suffix
+  const versionNumber = isCurrent ? resolvedVersion : 
resolvedVersion.replace("-incubating", "");
+  const versionOrigin = isCurrent ? resolvedVersion : originVersion;
+  const versionReference = isCurrent ? "next" : 
referenceVersion(resolvedVersion);
+  const pythonArg = isCurrent ? originVersion : resolvedVersion;
+
+  const replacements: Replacement[] = [
+    [/@pulsar:version_number@/g, versionNumber],
+    [/@pulsar:version@/g, resolvedVersion],
+    [/@pulsar:version_origin@/g, versionOrigin],
+    [/@pulsar:version_reference@/g, versionReference],
+    [/pulsar:binary_release_url/g, binaryReleaseUrl(resolvedVersion)],
+    [/pulsar:connector_release_url/g, connectorReleaseUrl(resolvedVersion)],
+    [/pulsar:offloader_release_url/g, offloaderReleaseUrl(resolvedVersion)],
+    [/pulsar:presto_pulsar_connector_release_url/g, 
prestoPulsarReleaseUrl(resolvedVersion)],
+    [/pulsar:download_page_url/g, downloadPageUrl()],
+    [/@pulsar:rpm:client@/g, rpmDistUrl(resolvedVersion, "")],
+    [/@pulsar:rpm:client-debuginfo@/g, rpmDistUrl(resolvedVersion, 
"-debuginfo")],
+    [/@pulsar:rpm:client-devel@/g, rpmDistUrl(resolvedVersion, "-devel")],
+    [/@pulsar:deb:client@/g, debDistUrl(resolvedVersion, "")],
+    [/@pulsar:deb:client-devel@/g, debDistUrl(resolvedVersion, "-dev")],
+    [/@pulsar:dist_rpm:client@/g, rpmDistUrl(resolvedVersion, "")],
+    [/@pulsar:dist_rpm:client-debuginfo@/g, rpmDistUrl(resolvedVersion, 
"-debuginfo")],
+    [/@pulsar:dist_rpm:client-devel@/g, rpmDistUrl(resolvedVersion, "-devel")],
+    [/@pulsar:dist_deb:client@/g, debDistUrl(resolvedVersion, "")],
+    [/@pulsar:dist_deb:client-devel@/g, debDistUrl(resolvedVersion, "-dev")],
+    [/@pulsar:version:python@/g, clientPythonVersion(pythonArg)],
+    [/@pulsar:apidoc:python@/g, clientPythonVersionUrl(pythonArg)],
+    [/@pulsar:apidoc:cpp@/g, clientCPPVersionUrl(pythonArg)],
+    [/\(\/api\/pulsar-functions/g, javadocVersionUrl(originVersion, 
"pulsar-functions")],
+    [/\(\/api\/client/g, javadocVersionUrl(originVersion, "client")],
+    [/\(\/api\/admin/g, javadocVersionUrl(originVersion, "admin")],
+    [/@pulsar:version_number@/g, resolvedVersion],
+    [/@pulsar:version:adapters@/g, pulsarAdaptersVersion]
+  ];
+
+  return replacements;
+}
diff --git a/src/server/markdownPreprocessors/inject.ts 
b/src/server/markdownPreprocessors/inject.ts
new file mode 100644
index 00000000000..19ea2515484
--- /dev/null
+++ b/src/server/markdownPreprocessors/inject.ts
@@ -0,0 +1,84 @@
+// Expands {@inject:...} tokens in markdown source into markdown links.
+
+// eslint-disable-next-line @typescript-eslint/no-var-requires
+const urlConfig: {
+  javadocUrl: string;
+  restApiUrl: string;
+  functionsApiUrl: string;
+  sourceApiUrl: string;
+  sinkApiUrl: string;
+  packagesApiUrl: string;
+  githubUrl: string;
+  restApiBaseUrlMapping: Record<string, string>;
+} = require("../../../site-baseurls");
+
+const {
+  javadocUrl,
+  restApiUrl,
+  functionsApiUrl,
+  sourceApiUrl,
+  sinkApiUrl,
+  packagesApiUrl,
+  githubUrl,
+  restApiBaseUrlMapping,
+} = urlConfig;
+
+function injectLinkParse(prefix: string, name: string, path: string): {link: 
string; text: string} {
+  if (prefix === "javadoc") return {link: javadocUrl + path, text: name};
+  if (prefix === "github") return {link: githubUrl + "/tree/master/" + path, 
text: name};
+  if (prefix === "rest") return {link: restApiUrl + "#" + path, text: name};
+  if (prefix === "functions") return {link: functionsApiUrl + "#" + path, 
text: name};
+  if (prefix === "source") return {link: sourceApiUrl + "#" + path, text: 
name};
+  if (prefix === "sink") return {link: sinkApiUrl + "#" + path, text: name};
+  if (prefix === "packages") return {link: packagesApiUrl + "#" + path, text: 
name};
+  return {link: path, text: name};
+}
+
+function injectLinkParseForEndpoint(info: string): {text: string; link: 
string} {
+  const parts = info.split("|");
+  const method = parts[0];
+  let path = parts[1];
+  const suffix = parts[2] || "";
+
+  const restPath = path.split("/");
+  const restApiVersion = restPath[2];
+  const restApiType = restPath[3];
+  const restBaseUrl = restApiBaseUrlMapping[restApiType] || restApiUrl;
+
+  let restUrl: string;
+  if (suffix.indexOf("?version=") >= 0) {
+    const [suffixPart, versionPart] = suffix.split("?version=");
+    restUrl = 
`version=${versionPart}&apiversion=${restApiVersion}#${suffixPart}`;
+    if (suffixPart.startsWith("operation/")) {
+      path += suffixPart.slice("operation".length);
+    }
+  } else {
+    restUrl = `version=master&apiversion=${restApiVersion}#${suffix}`;
+    if (suffix.startsWith("operation/")) {
+      path += suffix.slice("operation".length);
+    }
+  }
+
+  return {
+    text: `${method} ${path}`,
+    link: `${restBaseUrl}?${restUrl}`,
+  };
+}
+
+type Args = {filePath: string; fileContent: string};
+
+export default function injectPreprocessor({fileContent}: Args): string {
+  return fileContent.replaceAll(/{@inject:([^}]+)}/g, (_match, p1: string) => {
+    const trimmed = p1.trim();
+    const endpointPrefix = "endpoint|";
+    let link: string;
+    let text: string;
+    if (trimmed.startsWith(endpointPrefix)) {
+      ({link, text} = 
injectLinkParseForEndpoint(trimmed.substring(endpointPrefix.length)));
+    } else {
+      const [prefix, name, path] = trimmed.split(":");
+      ({link, text} = injectLinkParse(prefix, name, path));
+    }
+    return `[${text}](${link})`;
+  });
+}
diff --git a/src/server/markdownPreprocessors/pulsarVariables.ts 
b/src/server/markdownPreprocessors/pulsarVariables.ts
new file mode 100644
index 00000000000..ca520eba833
--- /dev/null
+++ b/src/server/markdownPreprocessors/pulsarVariables.ts
@@ -0,0 +1,24 @@
+import {resolveTokens} from "../../config/pulsarVariables";
+
+// Only docs/**/*.md and versioned_docs/version-*/**/*.md are rewritten.
+// Blog posts, src/pages/*.mdx, and the auxiliary content-docs plugins
+// (contribute/release-notes/security/client-feature-matrix) are skipped so
+// the link-targeting regexes (e.g. `[text](/tools/pulsar...)`) can't rewrite
+// legitimately-authored markdown links.
+const SCOPE_RE = 
/(?:^|[/\\])(?:docs|versioned_docs[/\\]version-[^/\\]+)[/\\].*\.md$/;
+const VERSIONED_RE = /(?:^|[/\\])versioned_docs[/\\]version-([^/\\]+)[/\\]/;
+
+type Args = {filePath: string; fileContent: string};
+
+export default function pulsarVariablesPreprocessor({filePath, fileContent}: 
Args): string {
+  if (!SCOPE_RE.test(filePath)) {
+    return fileContent;
+  }
+  const versionMatch = filePath.match(VERSIONED_RE);
+  const versionKey = versionMatch ? versionMatch[1] : "current";
+  let result = fileContent;
+  for (const [regex, replacement] of resolveTokens(versionKey)) {
+    result = result.replace(regex, replacement);
+  }
+  return result;
+}
diff --git a/tools/pytools/lib/execute/site_builder.py 
b/tools/pytools/lib/execute/site_builder.py
index 6c8fa4819cb..95595ac2c89 100644
--- a/tools/pytools/lib/execute/site_builder.py
+++ b/tools/pytools/lib/execute/site_builder.py
@@ -35,10 +35,8 @@ def execute(asf_site: Path):
 
     # # 2. Install and build
     yarn = find_command('yarn', msg="yarn is required")
-    node = find_command('node', msg="node is required")
     bash = find_command('bash', msg="bash is required")
     run(yarn, 'install', cwd=site_path())
-    run(node, 'scripts/replace.js', cwd=site_path())
     run(bash, 'scripts/split-version-build.sh', *modified_files, 
cwd=site_path())
     latest_content = site_path() / 'build'
 
diff --git a/versioned_docs/version-3.0.x/reference-configuration.md 
b/versioned_docs/version-3.0.x/reference-configuration.md
index e60d9b6348a..653a28e5b92 100644
--- a/versioned_docs/version-3.0.x/reference-configuration.md
+++ b/versioned_docs/version-3.0.x/reference-configuration.md
@@ -4,4 +4,4 @@ title: Pulsar Configuration
 sidebar_label: "Pulsar Configuration"
 ---
 
-For a complete list of Pulsar configuration, visit the [Pulsar 
Reference](pathname:///reference/#/@pulsar:version_reference@/) website.
+For a complete list of Pulsar configuration, visit the <a 
href="pathname:///reference/#/@pulsar:version_reference@/" target="_blank" 
rel="noopener noreferrer">Pulsar Reference</a> website.
diff --git a/versioned_docs/version-4.0.x/reference-configuration.md 
b/versioned_docs/version-4.0.x/reference-configuration.md
index e60d9b6348a..653a28e5b92 100644
--- a/versioned_docs/version-4.0.x/reference-configuration.md
+++ b/versioned_docs/version-4.0.x/reference-configuration.md
@@ -4,4 +4,4 @@ title: Pulsar Configuration
 sidebar_label: "Pulsar Configuration"
 ---
 
-For a complete list of Pulsar configuration, visit the [Pulsar 
Reference](pathname:///reference/#/@pulsar:version_reference@/) website.
+For a complete list of Pulsar configuration, visit the <a 
href="pathname:///reference/#/@pulsar:version_reference@/" target="_blank" 
rel="noopener noreferrer">Pulsar Reference</a> website.
diff --git a/versioned_docs/version-4.2.x/reference-configuration.md 
b/versioned_docs/version-4.2.x/reference-configuration.md
index e60d9b6348a..653a28e5b92 100644
--- a/versioned_docs/version-4.2.x/reference-configuration.md
+++ b/versioned_docs/version-4.2.x/reference-configuration.md
@@ -4,4 +4,4 @@ title: Pulsar Configuration
 sidebar_label: "Pulsar Configuration"
 ---
 
-For a complete list of Pulsar configuration, visit the [Pulsar 
Reference](pathname:///reference/#/@pulsar:version_reference@/) website.
+For a complete list of Pulsar configuration, visit the <a 
href="pathname:///reference/#/@pulsar:version_reference@/" target="_blank" 
rel="noopener noreferrer">Pulsar Reference</a> website.
diff --git a/yarn.lock b/yarn.lock
index 0fa745a0ce9..a1f16c12cc3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -10220,13 +10220,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"fs.realpath@npm:^1.0.0":
-  version: 1.0.0
-  resolution: "fs.realpath@npm:1.0.0"
-  checksum: 
99ddea01a7e75aa276c250a04eedeffe5662bce66c65c07164ad6264f9de18fb21be9433ead460e54cff20e31721c811f4fb5d70591799df5f85dce6d6746fd0
-  languageName: node
-  linkType: hard
-
 "fsevents@npm:~2.3.2":
   version: 2.3.3
   resolution: "fsevents@npm:2.3.3"
@@ -10465,20 +10458,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"glob@npm:^7.2.0":
-  version: 7.2.3
-  resolution: "glob@npm:7.2.3"
-  dependencies:
-    fs.realpath: ^1.0.0
-    inflight: ^1.0.4
-    inherits: 2
-    minimatch: ^3.1.1
-    once: ^1.3.0
-    path-is-absolute: ^1.0.0
-  checksum: 
29452e97b38fa704dabb1d1045350fb2467cf0277e155aa9ff7077e90ad81d1ea9d53d3ee63bd37c05b09a065e90f16aec4a65f5b8de401d1dac40bc5605d133
-  languageName: node
-  linkType: hard
-
 "global-dirs@npm:^3.0.0":
   version: 3.0.1
   resolution: "global-dirs@npm:3.0.1"
@@ -11302,30 +11281,20 @@ __metadata:
   languageName: node
   linkType: hard
 
-"inflight@npm:^1.0.4":
-  version: 1.0.6
-  resolution: "inflight@npm:1.0.6"
-  dependencies:
-    once: ^1.3.0
-    wrappy: 1
-  checksum: 
f4f76aa072ce19fae87ce1ef7d221e709afb59d445e05d47fba710e85470923a75de35bfae47da6de1b18afc3ce83d70facf44cfb0aff89f0a3f45c0a0244dfd
+"inherits@npm:2.0.3":
+  version: 2.0.3
+  resolution: "inherits@npm:2.0.3"
+  checksum: 
78cb8d7d850d20a5e9a7f3620db31483aa00ad5f722ce03a55b110e5a723539b3716a3b463e2b96ce3fe286f33afc7c131fa2f91407528ba80cea98a7545d4c0
   languageName: node
   linkType: hard
 
-"inherits@npm:2, inherits@npm:^2.0.1, inherits@npm:^2.0.3, 
inherits@npm:~2.0.3, inherits@npm:~2.0.4":
+"inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:~2.0.3, 
inherits@npm:~2.0.4":
   version: 2.0.4
   resolution: "inherits@npm:2.0.4"
   checksum: 
4a48a733847879d6cf6691860a6b1e3f0f4754176e4d71494c41f3475553768b10f84b5ce1d40fbd0e34e6bfbb864ee35858ad4dd2cf31e02fc4a154b724d7f1
   languageName: node
   linkType: hard
 
-"inherits@npm:2.0.3":
-  version: 2.0.3
-  resolution: "inherits@npm:2.0.3"
-  checksum: 
78cb8d7d850d20a5e9a7f3620db31483aa00ad5f722ce03a55b110e5a723539b3716a3b463e2b96ce3fe286f33afc7c131fa2f91407528ba80cea98a7545d4c0
-  languageName: node
-  linkType: hard
-
 "ini@npm:2.0.0":
   version: 2.0.0
   resolution: "ini@npm:2.0.0"
@@ -13834,7 +13803,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"minimatch@npm:3.1.5, minimatch@npm:^3.1.1":
+"minimatch@npm:3.1.5":
   version: 3.1.5
   resolution: "minimatch@npm:3.1.5"
   dependencies:
@@ -14529,15 +14498,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"once@npm:^1.3.0":
-  version: 1.4.0
-  resolution: "once@npm:1.4.0"
-  dependencies:
-    wrappy: 1
-  checksum: 
cd0a88501333edd640d95f0d2700fbde6bff20b3d4d9bdc521bdd31af0656b5706570d6c6afe532045a20bb8dc0849f8332d6f2a416e0ba6d3d3b98806c7db68
-  languageName: node
-  linkType: hard
-
 "onetime@npm:^5.1.2":
   version: 5.1.2
   resolution: "onetime@npm:5.1.2"
@@ -14846,13 +14806,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"path-is-absolute@npm:^1.0.0":
-  version: 1.0.1
-  resolution: "path-is-absolute@npm:1.0.1"
-  checksum: 
060840f92cf8effa293bcc1bea81281bd7d363731d214cbe5c227df207c34cd727430f70c6037b5159c8a870b9157cba65e775446b0ab06fd5ecc7e54615a3b8
-  languageName: node
-  linkType: hard
-
 "path-is-inside@npm:1.0.2":
   version: 1.0.2
   resolution: "path-is-inside@npm:1.0.2"
@@ -17130,19 +17083,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"replace-in-file@npm:6.3.2":
-  version: 6.3.2
-  resolution: "replace-in-file@npm:6.3.2"
-  dependencies:
-    chalk: ^4.1.2
-    glob: ^7.2.0
-    yargs: ^17.2.1
-  bin:
-    replace-in-file: bin/cli.js
-  checksum: 
ae3a0486711edfc1d7d769782764902934aeb327e54a56fbf8a92df22862a56312d86dbe0067274fb3666da668457576f6a775f814742acceea70f5aedb01f49
-  languageName: node
-  linkType: hard
-
 "require-directory@npm:^2.1.1":
   version: 2.1.1
   resolution: "require-directory@npm:2.1.1"
@@ -19614,7 +19554,6 @@ __metadata:
     react-svg: ^16.1.34
     rehype-katex: ^7.0.1
     remark-math: ^6.0.0
-    replace-in-file: 6.3.2
     search-insights: ^2.17.3
     semver: ^7.7.4
     sine-waves: ^0.3.0
@@ -19776,13 +19715,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"wrappy@npm:1":
-  version: 1.0.2
-  resolution: "wrappy@npm:1.0.2"
-  checksum: 
159da4805f7e84a3d003d8841557196034155008f817172d4e986bd591f74aa82aa7db55929a54222309e01079a65a92a9e6414da5a6aa4b01ee44a511ac3ee5
-  languageName: node
-  linkType: hard
-
 "write-file-atomic@npm:^3.0.3":
   version: 3.0.3
   resolution: "write-file-atomic@npm:3.0.3"
@@ -19912,7 +19844,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"yargs@npm:^17.0.0, yargs@npm:^17.2.1":
+"yargs@npm:^17.0.0":
   version: 17.7.2
   resolution: "yargs@npm:17.7.2"
   dependencies:


Reply via email to