fix(api-docs): fix Hugo spec path, sidebar data, and test selectors

- Move Hugo spec (relative links) to _build/ instead of static/ with
  -hugo- suffix, preserving original basenames for tag spec filenames
- Add articleDataKey, articleSection, and specDownloadPath to generated
  section and tag page frontmatter for sidebar nav and download button
- Fix sidebar link test to use #nav-tree instead of .sidebar (avoids
  matching the hamburger toggle link)
api-docs-uplift
Jason Stirnaman 2026-03-16 16:30:37 -05:00
parent 1c9d1a839a
commit 11e4913a58
3 changed files with 62 additions and 26 deletions

View File

@ -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 {

View File

@ -492,6 +492,8 @@ interface GenerateTagPagesOptions {
skipParentMenu?: boolean;
/** Map of API path to path-specific spec file (for single-operation rendering) */
pathSpecFiles?: Map<string, string>;
/** 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({

View File

@ -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', /^\/[^/]+\/[^/]+\/$/);