From 120d5b5df6dafbe01ae8001434282adb6eaf27e4 Mon Sep 17 00:00:00 2001 From: philip-ellis-sp Date: Wed, 24 Jun 2026 12:44:54 -0400 Subject: [PATCH] added redirect plugin for old apis --- api-redirects-plugin.ts | 100 ++++++++++++++++++++++++++++++++++++++++ package-lock.json | 8 ++++ package.json | 1 + plugins.ts | 2 + 4 files changed, 111 insertions(+) create mode 100644 api-redirects-plugin.ts diff --git a/api-redirects-plugin.ts b/api-redirects-plugin.ts new file mode 100644 index 0000000000000..6427a9e2a681b --- /dev/null +++ b/api-redirects-plugin.ts @@ -0,0 +1,100 @@ +import fs from 'fs'; +import path from 'path'; +import yaml from 'js-yaml'; + +type RedirectEntry = {from: string; to: string}; + +// Maps each versioned docs directory to its corresponding Redoc HTML file. +// Update this map when new API versions or Redoc files are added. +const API_VERSION_MAP: Record = { + beta: 'sailpoint-api-beta-light.html', + v3: 'sailpoint-api-v3-light.html', + v2024: 'sailpoint-api-v2024-light.html', + v2025: 'sailpoint-api-v2025-light.html', + v2026: 'sailpoint-api-v2026-light.html', +}; + +function camelToKebab(str: string): string { + return str.replace(/([A-Z])/g, (m) => '-' + m.toLowerCase()); +} + +function buildApiRedirects(): RedirectEntry[] { + const redirects: RedirectEntry[] = []; + const specBase = path.join(__dirname, 'static', 'api-specs', 'idn'); + + for (const [version, redocFile] of Object.entries(API_VERSION_MAP)) { + const specFile = path.join(specBase, `sailpoint-api.${version}.yaml`); + let spec: any; + try { + spec = yaml.load(fs.readFileSync(specFile, 'utf8')) as any; + } catch { + continue; + } + + for (const pathObj of Object.values(spec.paths ?? {})) { + const ref = (pathObj as any)['$ref']; + if (!ref) continue; + const refFile = path.resolve(specBase, ref); + let pathSpec: any; + try { + pathSpec = yaml.load(fs.readFileSync(refFile, 'utf8')) as any; + } catch { + continue; + } + + for (const [method, op] of Object.entries(pathSpec ?? {})) { + if (!['get', 'post', 'put', 'patch', 'delete', 'head', 'options'].includes(method)) continue; + const operation = op as any; + if (!operation?.operationId) continue; + const tag: string = (operation.tags ?? [])[0]; + if (!tag) continue; + + const slug = camelToKebab(operation.operationId); + const tagSegment = tag.replace(/\s+/g, '-'); + redirects.push({ + from: `/docs/api/${version}/${slug}`, + to: `/redoc/${redocFile}#tag/${tagSegment}/operation/${operation.operationId}`, + }); + } + } + } + + return redirects; +} + +function buildApiRedirectHtml(targetUrl: string): string { + return ` + + + + + + + + +

Redirecting to ${targetUrl}

+ +`; +} + +// Docusaurus plugin — writes static HTML redirect pages during production +// builds so old versioned API doc URLs keep resolving after those MDX pages +// are removed and replaced by the unified Redoc HTML files. +export default function apiRedirectsPlugin() { + return { + name: 'api-redirects-plugin', + + async postBuild({outDir}: {outDir: string}) { + const redirects = buildApiRedirects(); + let written = 0; + for (const {from, to} of redirects) { + const filePath = path.join(outDir, from, 'index.html'); + if (fs.existsSync(filePath)) continue; // never overwrite an active page + fs.mkdirSync(path.dirname(filePath), {recursive: true}); + fs.writeFileSync(filePath, buildApiRedirectHtml(to), 'utf8'); + written++; + } + console.log(`[api-redirects-plugin] Wrote ${written} redirect pages.`); + }, + }; +} diff --git a/package-lock.json b/package-lock.json index 37f668afaa35d..b0cc6c66e6f52 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,6 +58,7 @@ "@docusaurus/preset-classic": "3.9.2", "@docusaurus/tsconfig": "3.9.2", "@docusaurus/types": "3.9.2", + "@types/js-yaml": "^4.0.9", "dotenv": "^16.4.5", "esbuild": "^0.25.0", "husky": "^8.0.2", @@ -7401,6 +7402,13 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", diff --git a/package.json b/package.json index e0fab90e37776..1f1e48419e782 100644 --- a/package.json +++ b/package.json @@ -109,6 +109,7 @@ "@docusaurus/preset-classic": "3.9.2", "@docusaurus/tsconfig": "3.9.2", "@docusaurus/types": "3.9.2", + "@types/js-yaml": "^4.0.9", "dotenv": "^16.4.5", "esbuild": "^0.25.0", "husky": "^8.0.2", diff --git a/plugins.ts b/plugins.ts index 93d4ed0b61331..37af8432df5ef 100644 --- a/plugins.ts +++ b/plugins.ts @@ -1,7 +1,9 @@ const {createApiPageMD} = require('./createApiPageMD'); import clsx from 'clsx'; +import apiRedirectsPlugin from './api-redirects-plugin'; const pluginConfig = [ + [apiRedirectsPlugin, {}], [ function disableExpensiveBundlerOptimizationPlugin() { return {