From c3c4ed38c5a808f85bba349d5d6895d2f7c3c1ef Mon Sep 17 00:00:00 2001 From: Alexander Trofimov Date: Fri, 6 Feb 2026 09:38:40 +0300 Subject: [PATCH] fix: improve path segment extraction for multi-instance docs Refactors the path extraction logic in both index.ts and sidebars/index.ts to use segment-by-segment matching instead of string splitting. This fixes edge cases where docPath appears multiple times in outputDir or when paths have inconsistent leading/trailing slashes. Changes: - Normalize paths by removing leading ./ and leading/trailing slashes - Match docPath segments within outputDir segments sequentially - Clean up double slashes in the resulting infoBasePath Fixes #1305 --- .../src/index.ts | 30 ++++++++++++------- .../src/sidebars/index.ts | 28 +++++++++++++---- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/packages/docusaurus-plugin-openapi-docs/src/index.ts b/packages/docusaurus-plugin-openapi-docs/src/index.ts index 2a198ce03..58636ebdf 100644 --- a/packages/docusaurus-plugin-openapi-docs/src/index.ts +++ b/packages/docusaurus-plugin-openapi-docs/src/index.ts @@ -368,17 +368,25 @@ custom_edit_url: null let infoBasePath = `${outputDir}/${item.infoId}`; if (docRouteBasePath) { // Safely extract path segment, handling cases where docPath may not be in outputDir - const outputSegment = - docPath && outputDir.includes(docPath) - ? (outputDir.split(docPath)[1]?.replace(/^\/+/g, "") ?? "") - : outputDir - .slice(outputDir.indexOf("/", 1)) - .replace(/^\/+/g, ""); - infoBasePath = - `${docRouteBasePath}/${outputSegment}/${item.infoId}`.replace( - /^\/+/g, - "" - ); + const normalize = (p: string): string => + p.replace(/^\.\//, "").replace(/^\/+|\/+$/g, ""); + const outputSegments = normalize(outputDir).split("/").filter(Boolean); + let outputSegment = outputSegments.slice(1).join("/"); // fallback + if (docPath) { + const docSegments = normalize(docPath).split("/").filter(Boolean); + for (let i = 0; i <= outputSegments.length - docSegments.length; i++) { + const match = docSegments.every( + (seg, j) => outputSegments[i + j] === seg + ); + if (match) { + outputSegment = outputSegments.slice(i + docSegments.length).join("/"); + break; + } + } + } + infoBasePath = `${docRouteBasePath}/${outputSegment}/${item.infoId}` + .replace(/^\/+/g, "") + .replace(/\/+/g, "/"); } if (item.infoId) item.infoPath = infoBasePath; } diff --git a/packages/docusaurus-plugin-openapi-docs/src/sidebars/index.ts b/packages/docusaurus-plugin-openapi-docs/src/sidebars/index.ts index 1f7376f81..30438f5a3 100644 --- a/packages/docusaurus-plugin-openapi-docs/src/sidebars/index.ts +++ b/packages/docusaurus-plugin-openapi-docs/src/sidebars/index.ts @@ -130,13 +130,29 @@ function groupByTags( output: string, doc: string | undefined ): string => { - if (doc && output.includes(doc)) { - return output.split(doc)[1]?.replace(/^\/+/g, "") ?? ""; + // Normalize path: remove leading ./ and leading/trailing slashes + const normalize = (p: string): string => + p.replace(/^\.\//, "").replace(/^\/+|\/+$/g, ""); + + const outputSegments = normalize(output).split("/").filter(Boolean); + + if (doc) { + const docSegments = normalize(doc).split("/").filter(Boolean); + + // Find where docSegments sequence appears in outputSegments + for (let i = 0; i <= outputSegments.length - docSegments.length; i++) { + const match = docSegments.every( + (seg, j) => outputSegments[i + j] === seg + ); + if (match) { + // Return everything after the matched sequence + return outputSegments.slice(i + docSegments.length).join("/"); + } + } } - const slashIndex = output.indexOf("/", 1); - return slashIndex === -1 - ? "" - : output.slice(slashIndex).replace(/^\/+/g, ""); + + // Fallback: return everything after first segment + return outputSegments.slice(1).join("/"); }; const basePath = getBasePathFromOutput(outputDir, docPath);