diff --git a/api-docs/scripts/dist/generate-openapi-articles.js b/api-docs/scripts/dist/generate-openapi-articles.js index ff08d584c..489816feb 100644 --- a/api-docs/scripts/dist/generate-openapi-articles.js +++ b/api-docs/scripts/dist/generate-openapi-articles.js @@ -380,6 +380,16 @@ function generateTagPagesFromArticleData(options) { if (!fs.existsSync(apiParentDir)) { fs.mkdirSync(apiParentDir, { recursive: true }); } + // Derive articleDataKey from staticDirName or articlesPath + const articleDataKey = options.staticDirName || path.basename(articlesPath); + // Derive articleSection from contentPath — the section is the last segment of the API URL + // e.g., "content/influxdb3/core" → generates pages in "api/", so section = "api" + // For management APIs: generates pages in "management-api/", so section = "management-api" + const articleSection = apiParentDir.includes('management-api') + ? 'management-api' + : 'api'; + // Derive specDownloadPath: /openapi/{staticDirName}.yml + const specDownloadPath = `/openapi/${articleDataKey}.yml`; if (!fs.existsSync(parentIndexFile)) { // Build description - use product description or generate from product name const apiDescription = productDescription || @@ -389,6 +399,9 @@ function generateTagPagesFromArticleData(options) { description: apiDescription, weight: 104, type: 'api', + articleDataKey, + articleSection, + specDownloadPath, }; // Add menu entry for parent page (unless skipParentMenu is true) if (menuKey && !skipParentMenu) { @@ -476,7 +489,11 @@ All {{% product-name %}} API endpoints, sorted by path. type: 'api', layout: isConceptual ? 'single' : 'list', staticFilePath: article.fields.staticFilePath, + specDownloadPath, weight, + // Sidebar data lookup fields + articleDataKey, + articleSection, // Tag-based fields tag: article.fields.tag, isConceptual, @@ -842,15 +859,11 @@ function absolutifyLinks(spec, baseUrl) { if (key && MARKDOWN_FIELDS.has(key)) { return value.replace(INTERNAL_LINK_RE, `$1${baseUrl}$2`); } - if (key === 'href' && - parent?.title && - value.startsWith('/')) { + if (key === 'href' && parent?.title && value.startsWith('/')) { return `${baseUrl}${value}`; } // externalDocs.url — standard OpenAPI field - if (key === 'url' && - parent?.description && - value.startsWith('/')) { + if (key === 'url' && parent?.description && value.startsWith('/')) { return `${baseUrl}${value}`; } return value; @@ -988,8 +1001,11 @@ function processSpecFile(specConfig, staticPath, staticDirName, productKey) { linkErrors.forEach((err) => console.warn(` ${err}`)); } } - // Write Hugo spec (relative links) for article generation and templates - const hugoSpecPath = staticSpecPath.replace(/\.yml$/, '-hugo.yml'); + // Write Hugo spec (relative links) to _build/ for article generation. + // Tag specs and frontmatter staticFilePath derive names from this path, + // so it must match the final staticSpecPath basename. + const hugoSpecPath = path.join(API_DOCS_ROOT, '_build', path.relative(path.join(DOCS_ROOT, 'static'), staticSpecPath)); + fs.mkdirSync(path.dirname(hugoSpecPath), { recursive: true }); fs.writeFileSync(hugoSpecPath, yaml.dump(transformedSpec)); // Absolutify links for downloadable specs (relative paths → full URLs) const downloadSpec = absolutifyLinks(transformedSpec, DOCS_BASE_URL); @@ -1111,6 +1127,7 @@ function processProduct(productKey, config) { menuParent: 'InfluxDB HTTP API', skipParentMenu: config.skipParentMenu, pathSpecFiles: allPathSpecFiles, + staticDirName, }); } else { diff --git a/api-docs/scripts/generate-openapi-articles.ts b/api-docs/scripts/generate-openapi-articles.ts index ea55e4471..60611bec7 100644 --- a/api-docs/scripts/generate-openapi-articles.ts +++ b/api-docs/scripts/generate-openapi-articles.ts @@ -492,6 +492,8 @@ interface GenerateTagPagesOptions { skipParentMenu?: boolean; /** Map of API path to path-specific spec file (for single-operation rendering) */ pathSpecFiles?: Map; + /** Static directory name for download path derivation (e.g., 'influxdb3-core') */ + staticDirName?: string; } /** @@ -563,6 +565,19 @@ function generateTagPagesFromArticleData( fs.mkdirSync(apiParentDir, { recursive: true }); } + // Derive articleDataKey from staticDirName or articlesPath + const articleDataKey = options.staticDirName || path.basename(articlesPath); + + // Derive articleSection from contentPath — the section is the last segment of the API URL + // e.g., "content/influxdb3/core" → generates pages in "api/", so section = "api" + // For management APIs: generates pages in "management-api/", so section = "management-api" + const articleSection = apiParentDir.includes('management-api') + ? 'management-api' + : 'api'; + + // Derive specDownloadPath: /openapi/{staticDirName}.yml + const specDownloadPath = `/openapi/${articleDataKey}.yml`; + if (!fs.existsSync(parentIndexFile)) { // Build description - use product description or generate from product name const apiDescription = @@ -574,6 +589,9 @@ function generateTagPagesFromArticleData( description: apiDescription, weight: 104, type: 'api', + articleDataKey, + articleSection, + specDownloadPath, }; // Add menu entry for parent page (unless skipParentMenu is true) @@ -680,7 +698,11 @@ All {{% product-name %}} API endpoints, sorted by path. type: 'api', layout: isConceptual ? 'single' : 'list', staticFilePath: article.fields.staticFilePath, + specDownloadPath, weight, + // Sidebar data lookup fields + articleDataKey, + articleSection, // Tag-based fields tag: article.fields.tag, isConceptual, @@ -1162,19 +1184,11 @@ function absolutifyLinks( if (key && MARKDOWN_FIELDS.has(key)) { return value.replace(INTERNAL_LINK_RE, `$1${baseUrl}$2`); } - if ( - key === 'href' && - parent?.title && - value.startsWith('/') - ) { + if (key === 'href' && parent?.title && value.startsWith('/')) { return `${baseUrl}${value}`; } // externalDocs.url — standard OpenAPI field - if ( - key === 'url' && - parent?.description && - value.startsWith('/') - ) { + if (key === 'url' && parent?.description && value.startsWith('/')) { return `${baseUrl}${value}`; } return value; @@ -1357,8 +1371,15 @@ function processSpecFile( } } - // Write Hugo spec (relative links) for article generation and templates - const hugoSpecPath = staticSpecPath.replace(/\.yml$/, '-hugo.yml'); + // Write Hugo spec (relative links) to _build/ for article generation. + // Tag specs and frontmatter staticFilePath derive names from this path, + // so it must match the final staticSpecPath basename. + const hugoSpecPath = path.join( + API_DOCS_ROOT, + '_build', + path.relative(path.join(DOCS_ROOT, 'static'), staticSpecPath) + ); + fs.mkdirSync(path.dirname(hugoSpecPath), { recursive: true }); fs.writeFileSync(hugoSpecPath, yaml.dump(transformedSpec)); // Absolutify links for downloadable specs (relative paths → full URLs) @@ -1368,10 +1389,7 @@ function processSpecFile( fs.writeFileSync(staticSpecPath, yaml.dump(downloadSpec)); console.log(`✓ Wrote downloadable spec to ${staticSpecPath}`); - fs.writeFileSync( - staticJsonSpecPath, - JSON.stringify(downloadSpec, null, 2) - ); + fs.writeFileSync(staticJsonSpecPath, JSON.stringify(downloadSpec, null, 2)); console.log(`✓ Generated JSON spec at ${staticJsonSpecPath}`); return { staticSpecPath, staticJsonSpecPath, hugoSpecPath, articlesPath }; @@ -1541,6 +1559,7 @@ function processProduct(productKey: string, config: ProductConfig): void { menuParent: 'InfluxDB HTTP API', skipParentMenu: config.skipParentMenu, pathSpecFiles: allPathSpecFiles, + staticDirName, }); } else { generatePagesFromArticleData({ diff --git a/cypress/e2e/content/api-reference.cy.js b/cypress/e2e/content/api-reference.cy.js index 318684d91..20a3d2c34 100644 --- a/cypress/e2e/content/api-reference.cy.js +++ b/cypress/e2e/content/api-reference.cy.js @@ -68,8 +68,8 @@ describe('API reference content', () => { ); }); it('has a sidebar link to the product home', function () { - // The sidebar contains a link to the product root page - cy.get('.sidebar a') + // The nav tree contains a link to the product root page + cy.get('#nav-tree a') .first() .should('have.attr', 'href') .and('match', /^\/[^/]+\/[^/]+\/$/);