@@ -2,6 +2,7 @@ import type { SeoPluginOptions } from "@vuepress/plugin-seo";
22import { match } from "ts-pattern" ;
33import type { App , HeadConfig , Page } from "vuepress" ;
44import { hostname } from "./shared" ;
5+ import { instance as versioning , type Version as VersionGroup } from "../../lib/versioning" ;
56
67interface DocumentationPath {
78 version : string | null ;
@@ -59,6 +60,100 @@ const normalize = (str: string): string =>
5960 . map ( word => word . charAt ( 0 ) . toUpperCase ( ) + word . slice ( 1 ) )
6061 . join ( ' ' ) ;
6162
63+ /**
64+ * Finds the version group for a given section path.
65+ * @param section The section path (e.g., "server", "clients/dotnet", "server/kubernetes-operator").
66+ * @returns The version group or null if not found.
67+ */
68+ const findVersionGroup = ( section : string ) : VersionGroup | null => {
69+ // Special case for kubernetes-operator
70+ if ( section === "server/kubernetes-operator" ) {
71+ return versioning . all . find ( ( v ) => v . id === "kubernetes-operator" ) || null ;
72+ }
73+
74+ // Try to find by basePath match
75+ const byBasePath = versioning . all . find ( ( v ) => v . basePath === section ) ;
76+ if ( byBasePath ) {
77+ return byBasePath ;
78+ }
79+
80+ // Try to match client sections to their version group IDs
81+ const clientMatch = section . match ( / ^ c l i e n t s \/ ( \w + ) / ) ;
82+ if ( clientMatch ) {
83+ const clientName = clientMatch [ 1 ] ;
84+ const clientId = `${ clientName } -client` ;
85+ return versioning . all . find ( ( v ) => v . id === clientId ) || null ;
86+ }
87+
88+ return null ;
89+ } ;
90+
91+ /**
92+ * Gets the latest version string for a given section.
93+ * @param section The section path.
94+ * @returns The latest version string, or null if not found.
95+ */
96+ const getLatestVersionForSection = ( section : string ) : string | null => {
97+ const versionGroup = findVersionGroup ( section ) ;
98+ if ( ! versionGroup ?. versions ?. length ) {
99+ return null ;
100+ }
101+
102+ // Find the first non-preview, non-excluded version (which is the latest)
103+ const excludedVersions = EXCLUDED_VERSIONS [ section ] || [ ] ;
104+ const latestVersion = versionGroup . versions . find (
105+ v => v . version && ! v . preview && ! excludedVersions . includes ( v . version )
106+ ) ;
107+
108+ return latestVersion ?. version || null ;
109+ } ;
110+
111+ /**
112+ * Gets the docsearch:version content for a page.
113+ * @param section The section path.
114+ * @param currentVersion The current version string from the path.
115+ * @returns The version content string (e.g., "v1.2,latest" or "v1.1" or "v1.0,legacy").
116+ */
117+ const getDocSearchVersionContent = ( section : string , currentVersion : string | null ) : string | null => {
118+ if ( ! currentVersion ) return null ;
119+
120+ const parts : string [ ] = [ currentVersion ] ;
121+ const latestVersion = getLatestVersionForSection ( section ) ;
122+
123+ if ( latestVersion && currentVersion === latestVersion ) parts . push ( "latest" ) ;
124+
125+ const isLegacy = [ "legacy" , "tcp" ] . some ( seg => section . split ( "/" ) . includes ( seg ) ) ;
126+ if ( isLegacy ) parts . push ( "legacy" ) ;
127+
128+ return parts . join ( "," ) ;
129+ } ;
130+
131+ /**
132+ * Maps a section path to a docsearch product name.
133+ * @param section The section path (e.g., "clients/dotnet", "server").
134+ * @returns The product name for docsearch (e.g., "dotnet_sdk", "js_sdk", "server") or null if not in the list.
135+ */
136+ const getDocSearchProduct = ( section : string ) : string | null => {
137+ return match ( section )
138+ . with ( "clients/dotnet" , ( ) => "dotnet_sdk" )
139+ . with ( "clients/golang" , ( ) => "golang_sdk" )
140+ . with ( "clients/java" , ( ) => "java_sdk" )
141+ . with ( "clients/node" , ( ) => "js_sdk" )
142+ . with ( "clients/python" , ( ) => "python_sdk" )
143+ . with ( "clients/rust" , ( ) => "rust_sdk" )
144+ . with ( "clients/dotnet/legacy" , ( ) => "dotnet_sdk" )
145+ . with ( "clients/golang/legacy" , ( ) => "golang_sdk" )
146+ . with ( "clients/java/legacy" , ( ) => "java_sdk" )
147+ . with ( "clients/node/legacy" , ( ) => "js_sdk" )
148+ . with ( "clients/python/legacy" , ( ) => "python_sdk" )
149+ . with ( "clients/rust/legacy" , ( ) => "rust_sdk" )
150+ . with ( "clients/tcp/dotnet" , ( ) => "dotnet_sdk_tcp" )
151+ . with ( "cloud" , ( ) => "cloud" )
152+ . with ( "server/kubernetes-operator" , ( ) => "kubernetes_operator" )
153+ . with ( "server" , ( ) => "server" )
154+ . otherwise ( ( ) => null ) ;
155+ } ;
156+
62157export const seoPlugin : SeoPluginOptions = {
63158 hostname,
64159
@@ -96,6 +191,9 @@ export const seoPlugin: SeoPluginOptions = {
96191 * Sets the following tags:
97192 * e.g. <meta name="es:category" content=".NET Client" />
98193 * <meta name="es:version" content="v1.0" />
194+ * <meta name="docsearch:version" content="v1.0,v1.1,v1.2" />
195+ * <meta name="docsearch:language" content="en" />
196+ * <meta name="docsearch:product" content="dotnet_sdk" />
99197 *
100198 * If it's a legacy or tcp client, it will be labelled as "Legacy"
101199 */
@@ -131,5 +229,21 @@ export const seoPlugin: SeoPluginOptions = {
131229 . otherwise ( ( ) => normalize ( section ) ) ;
132230
133231 head . push ( [ "meta" , { name : "es:category" , content : category } ] ) ;
232+
233+ // Add DocSearch meta tags
234+ // Add language tag (defaulting to "en")
235+ head . push ( [ "meta" , { name : "docsearch:language" , content : "en" } ] ) ;
236+
237+ // Add product tag (only if section is in the list)
238+ const product = getDocSearchProduct ( section ) ;
239+ if ( product ) {
240+ head . push ( [ "meta" , { name : "docsearch:product" , content : product } ] ) ;
241+ }
242+
243+ // Add version tag with current version
244+ const docSearchVersion = getDocSearchVersionContent ( section , version ) ;
245+ if ( docSearchVersion ) {
246+ head . push ( [ "meta" , { name : "docsearch:version" , content : docSearchVersion } ] ) ;
247+ }
134248 }
135249} ;
0 commit comments