From 65503aea24526f1022c7b58927122bc24c9baa6f Mon Sep 17 00:00:00 2001 From: Adam Friedmann Date: Tue, 18 Nov 2025 21:39:13 +0200 Subject: [PATCH 1/4] docs processing and push --- .gitignore | 5 +- package.json | 6 + .../category-map.json | 4 + .../file-processing/docs-json-template.json | 62 ++ .../file-processing/file-processing.js | 245 +++++++ .../file-processing/styling.css | 107 +++ .../push-to-docs-repo.js | 174 +++++ .../typedoc-mintlify-content.js | 84 +++ .../typedoc-mintlify-linked-types.js | 212 ++++++ .../typedoc-mintlify-parameters.js | 260 ++++++++ .../typedoc-plugin/typedoc-mintlify-plugin.js | 161 +++++ .../typedoc-mintlify-returns.js | 614 ++++++++++++++++++ .../typedoc-plugin/typedoc-mintlify-utils.js | 11 + typedoc.json | 39 ++ writing-docs.md | 24 + 15 files changed, 2007 insertions(+), 1 deletion(-) create mode 100644 scripts/mintlify-post-processing/category-map.json create mode 100644 scripts/mintlify-post-processing/file-processing/docs-json-template.json create mode 100755 scripts/mintlify-post-processing/file-processing/file-processing.js create mode 100644 scripts/mintlify-post-processing/file-processing/styling.css create mode 100644 scripts/mintlify-post-processing/push-to-docs-repo.js create mode 100644 scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-content.js create mode 100644 scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-linked-types.js create mode 100644 scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-parameters.js create mode 100644 scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-plugin.js create mode 100644 scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-returns.js create mode 100644 scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-utils.js create mode 100644 typedoc.json create mode 100644 writing-docs.md diff --git a/.gitignore b/.gitignore index f655c8d..63dabdf 100644 --- a/.gitignore +++ b/.gitignore @@ -56,4 +56,7 @@ logs *.tsbuildinfo # Optional REPL history -.node_repl_history \ No newline at end of file +.node_repl_history + +# Docs +docs/ diff --git a/package.json b/package.json index 2b6cee7..a7675aa 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,10 @@ "test:e2e": "vitest run tests/e2e", "test:watch": "vitest", "test:coverage": "vitest run --coverage", + "create-docs": "npm run create-docs:generate && npm run create-docs:process", + "push-docs": "node scripts/mintlify-post-processing/push-to-docs-repo.js", + "create-docs:generate": "typedoc", + "create-docs:process": "node scripts/mintlify-post-processing/file-processing/file-processing.js", "prepublishOnly": "npm run build" }, "dependencies": { @@ -30,6 +34,8 @@ "dotenv": "^16.3.1", "eslint": "^8.54.0", "nock": "^13.4.0", + "typedoc": "^0.28.14", + "typedoc-plugin-markdown": "^4.9.0", "typescript": "^5.3.2", "vitest": "^1.0.0" }, diff --git a/scripts/mintlify-post-processing/category-map.json b/scripts/mintlify-post-processing/category-map.json new file mode 100644 index 0000000..c887ed1 --- /dev/null +++ b/scripts/mintlify-post-processing/category-map.json @@ -0,0 +1,4 @@ +{ + "functions": "Main Methods", + "interfaces": "Modules" +} \ No newline at end of file diff --git a/scripts/mintlify-post-processing/file-processing/docs-json-template.json b/scripts/mintlify-post-processing/file-processing/docs-json-template.json new file mode 100644 index 0000000..b0dc864 --- /dev/null +++ b/scripts/mintlify-post-processing/file-processing/docs-json-template.json @@ -0,0 +1,62 @@ +{ + "$schema": "https://mintlify.com/docs.json", + "theme": "mint", + "name": "Base44 Support Documentation", + "integrations": { + "mixpanel": { + "projectToken": "cc6e9e106e4b833fc3a3819c11b74138" + } + }, + "colors": { + "primary": "#FF5500", + "light": "#EEE2C0", + "dark": "#FF5500" + }, + "navigation": { + "tabs": [ + { + "tab": "SDK Reference", + "groups": [ + { + "group": "Main Methods", + "pages": [ + "content/functions/createClient", + "content/functions/createClientFromRequest", + "content/functions/getAccessToken", + "content/functions/saveAccessToken", + "content/functions/removeAccessToken", + "content/functions/getLoginUrl" + ] + }, + { + "group": "Modules", + "pages": ["content/interfaces/Auth"] + } + ] + } + ] + }, + "navbar": { + "links": [ + { + "label": "Support", + "href": "https://app.base44.com/support/conversations" + } + ], + "primary": { + "type": "button", + "label": "Base44", + "href": "https://base44.com/?utm_source=Mintlify&utm_medium=Main&utm_content=menu" + } + }, + "footer": { + "socials": { + "twitter": "https://x.com/base_44", + "discord": "https://discord.com/invite/ThpYPZpVts", + "linkedin": "https://www.linkedin.com/company/base44" + } + }, + "custom": { + "stylesheets": ["/styling.css"] + } +} diff --git a/scripts/mintlify-post-processing/file-processing/file-processing.js b/scripts/mintlify-post-processing/file-processing/file-processing.js new file mode 100755 index 0000000..2d2632b --- /dev/null +++ b/scripts/mintlify-post-processing/file-processing/file-processing.js @@ -0,0 +1,245 @@ +#!/usr/bin/env node + +/** + * Post-processing script for TypeDoc-generated MDX files + * + * TypeDoc now emits .mdx files directly, so this script: + * 1. Processes links to make them Mintlify-compatible + * 2. Removes files for linked types that should be suppressed + * 3. Cleans up the temporary linked types tracking file + * 4. Generates docs.json with navigation structure + * 5. Copies styling.css to docs directory + */ + +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const DOCS_DIR = path.join(__dirname, '..', '..', '..', 'docs'); +const CONTENT_DIR = path.join(DOCS_DIR, 'content'); +const LINKED_TYPES_FILE = path.join(DOCS_DIR, '.linked-types.json'); +const TEMPLATE_PATH = path.join(__dirname, 'docs-json-template.json'); +const STYLING_CSS_PATH = path.join(__dirname, 'styling.css'); +const CATEGORY_MAP_PATH = path.join(__dirname, '../category-map.json'); + +/** + * Get list of linked type names that should be suppressed + */ +function getLinkedTypeNames() { + try { + if (fs.existsSync(LINKED_TYPES_FILE)) { + const content = fs.readFileSync(LINKED_TYPES_FILE, 'utf-8'); + return new Set(JSON.parse(content)); + } + } catch (e) { + // If file doesn't exist or can't be read, return empty set + } + return new Set(); +} + +/** + * Process links in a file to make them Mintlify-compatible + */ +function processLinksInFile(filePath) { + let content = fs.readFileSync(filePath, 'utf-8'); + let modified = false; + + // Remove .md and .mdx extensions from markdown links + // This handles both relative and absolute paths + const linkRegex = /\[([^\]]+)\]\(([^)]+)(\.mdx?)\)/g; + const newContent = content.replace(linkRegex, (match, linkText, linkPath, ext) => { + modified = true; + return `[${linkText}](${linkPath})`; + }); + + if (modified) { + fs.writeFileSync(filePath, newContent, 'utf-8'); + return true; + } + + return false; +} + +/** + * Scan docs content directory and build navigation structure + */ +function scanDocsContent() { + const result = { + functions: [], + interfaces: [], + classes: [], + typeAliases: [], + }; + + const sections = ['functions', 'interfaces', 'classes', 'type-aliases']; + + for (const section of sections) { + const sectionDir = path.join(CONTENT_DIR, section); + if (!fs.existsSync(sectionDir)) continue; + + const files = fs.readdirSync(sectionDir); + const mdxFiles = files + .filter((file) => file.endsWith('.mdx')) + .map((file) => path.basename(file, '.mdx')) + .sort() + .map((fileName) => `content/${section}/${fileName}`); + + const key = section === 'type-aliases' ? 'typeAliases' : section; + result[key] = mdxFiles; + } + + return result; +} + + +/** + * Get group name for a section, using category map or default + */ +function getGroupName(section, categoryMap) { + if (categoryMap[section]) { + return categoryMap[section]; + } + + return section; +} + +/** + * Generate docs.json from template and scanned content + */ +function generateDocsJson(docsContent) { + const template = JSON.parse(fs.readFileSync(TEMPLATE_PATH, 'utf-8')); + let categoryMap = {}; + try { + categoryMap = JSON.parse(fs.readFileSync(CATEGORY_MAP_PATH, 'utf-8')); + } catch (e) { + // If file doesn't exist or can't be read, return empty object + console.error(`Error: Category map file not found: ${CATEGORY_MAP_PATH}`); + } + + const groups = []; + + if (docsContent.functions.length > 0) { + groups.push({ + group: getGroupName('functions', categoryMap), + pages: docsContent.functions, + }); + } + + if (docsContent.interfaces.length > 0) { + groups.push({ + group: getGroupName('interfaces', categoryMap), + pages: docsContent.interfaces, + }); + } + + if (docsContent.classes.length > 0) { + groups.push({ + group: getGroupName('classes', categoryMap), + pages: docsContent.classes, + }); + } + + if (docsContent.typeAliases.length > 0) { + groups.push({ + group: getGroupName('typeAliases', categoryMap), + pages: docsContent.typeAliases, + }); + } + + // Find or create SDK Reference tab + let sdkTab = template.navigation.tabs.find(tab => tab.tab === 'SDK Reference'); + if (!sdkTab) { + sdkTab = { tab: 'SDK Reference', groups: [] }; + template.navigation.tabs.push(sdkTab); + } + + sdkTab.groups = groups; + + const docsJsonPath = path.join(DOCS_DIR, 'docs.json'); + fs.writeFileSync(docsJsonPath, JSON.stringify(template, null, 2) + '\n', 'utf-8'); + console.log(`Generated docs.json`); +} + +/** + * Copy styling.css to docs directory + */ +function copyStylingCss() { + const targetPath = path.join(DOCS_DIR, 'styling.css'); + fs.copyFileSync(STYLING_CSS_PATH, targetPath); + console.log(`Copied styling.css`); +} + +/** + * Recursively process all MDX files + */ +function processAllFiles(dir, linkedTypeNames) { + const entries = fs.readdirSync(dir, { withFileTypes: true }); + + for (const entry of entries) { + const entryPath = path.join(dir, entry.name); + + if (entry.isDirectory()) { + processAllFiles(entryPath, linkedTypeNames); + } else if (entry.isFile() && (entry.name.endsWith('.mdx') || entry.name.endsWith('.md'))) { + // Extract the type name from the file path + // e.g., "docs/interfaces/LoginViaEmailPasswordResponse.mdx" -> "LoginViaEmailPasswordResponse" + const fileName = path.basename(entryPath, path.extname(entryPath)); + + // Remove suppressed linked type files + if (linkedTypeNames.has(fileName)) { + fs.unlinkSync(entryPath); + const relativePath = path.relative(DOCS_DIR, entryPath); + console.log(`Removed (suppressed): ${relativePath}`); + } else { + // Process links in the file + if (processLinksInFile(entryPath)) { + const relativePath = path.relative(DOCS_DIR, entryPath); + console.log(`Processed links: ${relativePath}`); + } + } + } + } +} + +/** + * Main function + */ +function main() { + console.log('Processing TypeDoc MDX files for Mintlify...\n'); + + if (!fs.existsSync(DOCS_DIR)) { + console.error(`Error: Documentation directory not found: ${DOCS_DIR}`); + console.error('Please run "npm run docs:generate" first.'); + process.exit(1); + } + + // Get list of linked types to suppress + const linkedTypeNames = getLinkedTypeNames(); + + // Process all files (remove suppressed ones and fix links) + processAllFiles(DOCS_DIR, linkedTypeNames); + + // Clean up the linked types file + try { + if (fs.existsSync(LINKED_TYPES_FILE)) { + fs.unlinkSync(LINKED_TYPES_FILE); + } + } catch (e) { + // Ignore errors + } + + // Scan content and generate docs.json + const docsContent = scanDocsContent(); + generateDocsJson(docsContent); + + // Copy styling.css + copyStylingCss(); + + console.log(`\n✓ Post-processing complete!`); + console.log(` Documentation directory: ${DOCS_DIR}`); +} + +main(); diff --git a/scripts/mintlify-post-processing/file-processing/styling.css b/scripts/mintlify-post-processing/file-processing/styling.css new file mode 100644 index 0000000..430509e --- /dev/null +++ b/scripts/mintlify-post-processing/file-processing/styling.css @@ -0,0 +1,107 @@ +@import url('https://fonts.googleapis.com/css2?family=Wix+Madefor+Display:wght@400..800&family=Wix+Madefor+Text:ital,wght@0,400..800;1,400..800&display=swap'); + +/* Apply Wix Madefor Text for body text */ +body, +.mint-container { + font-family: "Wix Madefor Text", sans-serif; + font-weight: 400; + font-style: normal; + font-optical-sizing: auto; +} + +/* Apply Wix Madefor Display for headings */ +h1, h2, h3 { + font-family: "Wix Madefor Display", sans-serif; + font-weight: 600; + font-style: normal; + font-optical-sizing: auto; +} + +@media (prefers-color-scheme: light) { + body, + .mint-container, + h1, h2, h3 { + color: #111111; + } +} + + +/* Mintlify primary navbar CTA button color override */ +.mint-navbar .mint-button-primary, +.mint-navbar li#topbar-cta-button a > span.bg-primary-dark { + background-color: #FF5500 !important; /* updated brand orange */ + color: #111111 !important; + font-weight: 600; + border: none; +} + +/* If button has hover/focus/active states, ensure text stays black: */ +.mint-navbar .mint-navbarPrimaryButton:hover, +.mint-navbar .mint-navbarPrimaryButton:focus, +.mint-navbar .mint-navbarPrimaryButton:active, +.mint-navbar .mint-button-primary:hover, +.mint-navbar .mint-button-primary:focus, +.mint-navbar .mint-button-primary:active { + color: #111111 !important; +} + +/* Optional: Remove box-shadow on click, if present */ +.mint-navbar .mint-navbarPrimaryButton:active { + box-shadow: none !important; +} + +/* ---- NEW: Force the Base44 button text to black ---- */ +a[href*="base44.com"] span, +a[href*="base44.com"] .text-white, +.mint-navbar a span.text-white { + color: #111111 !important; +} +/* Force the ">" icon in the Base44 button to orange for consistency and accessibility */ +a[href*="base44.com"] svg, +a[href*="base44.com"] .text-white, +a[href*="base44.com"] svg path, +.mint-navbar a svg.text-white { + color: #111111 !important; /* Works if color is set by text utility */ + fill: #111111 !important; /* Ensures SVG/path fill is overridden */ +} +/* Restore thin chevron style for the navbar's "Base44" button arrow */ +a[href*="base44.com"] svg, +.mint-navbar a svg { + color: #111111 !important; + fill: none !important; + stroke: currentColor !important; + stroke-width: 2px !important; +} +a[href*="base44.com"] path, +.mint-navbar a svg path { + color: #111111 !important; + fill: none !important; + stroke: currentColor !important; + stroke-width: 2px !important; +} +/* Make the ">" icon (chevron) in Base44 navbar button thin and on-brand */ +a[href*="base44.com"] svg, +.mint-navbar a svg { + color: #111111 !important; +} + +a[href*="base44.com"] svg path, +.mint-navbar a svg path { + color: #111111 !important; + fill: none !important; + stroke: currentColor !important; + stroke-width: 1.5 !important; /* set to the original */ + stroke-linecap: round !important; /* keep the smooth end */ +} + +/* Optional: if you want it even thinner, try 1.2 or 1 */ + +/* Always use Mintlify's built-in theme variables! */ +div.prose.mt-1.font-normal.text-sm.leading-6.text-gray-600.dark\:text-gray-400, +div.prose.mt-1.font-normal.text-sm.leading-6.text-gray-600.dark\:text-gray-400 span, +.card .prose, +.card .prose span, +.mint-card .prose, +.mint-card .prose span { + color: var(--mint-text-secondary) !important; +} diff --git a/scripts/mintlify-post-processing/push-to-docs-repo.js b/scripts/mintlify-post-processing/push-to-docs-repo.js new file mode 100644 index 0000000..a9a4d04 --- /dev/null +++ b/scripts/mintlify-post-processing/push-to-docs-repo.js @@ -0,0 +1,174 @@ +#!/usr/bin/env node + +import fs from "fs"; +import path from "path"; +import os from "os"; +import { execSync } from "child_process"; + +console.debug = () => {}; // Disable debug logging. Comment this out to enable debug logging. + +const DOCS_SOURCE_PATH = path.join(import.meta.dirname, "../../docs/content"); +const TARGET_DOCS_REPO_URL = "git@github.com:base44-dev/mintlify-docs.git"; +const CATEGORY_MAP_PATH = path.join(import.meta.dirname, "./category-map.json"); + +function parseArgs() { + const args = process.argv.slice(2); + let branch = null; + + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + + if (arg === "--branch" && i + 1 < args.length) { + branch = args[++i]; + } + } + return { branch }; +} + +function scanSdkDocs(sdkDocsDir) { + const result = {}; + + // Get a list of all the subdirectories in the sdkDocsDir + const subdirectories = fs.readdirSync(sdkDocsDir).filter(file => fs.statSync(path.join(sdkDocsDir, file)).isDirectory()); + console.log(`Subdirectories: ${subdirectories}`); + + for (const subdirectory of subdirectories) { + const subdirectoryPath = path.join(sdkDocsDir, subdirectory); + const files = fs.readdirSync(subdirectoryPath).filter(file => file.endsWith(".mdx")); + result[subdirectory] = files.map(file => path.basename(file, ".mdx")); + } + return result; +} + +function updateDocsJson(repoDir, sdkFiles) { + const docsJsonPath = path.join(repoDir, 'docs.json'); + let categoryMap = {}; + try { + categoryMap = JSON.parse(fs.readFileSync(CATEGORY_MAP_PATH, 'utf8')); + } catch (e) { + console.error(`Error: Category map file not found: ${CATEGORY_MAP_PATH}`); + process.exit(1); + } + + console.log(`Reading docs.json from ${docsJsonPath}...`); + const docsContent = fs.readFileSync(docsJsonPath, 'utf8'); + const docs = JSON.parse(docsContent); + + // Find the "SDK Reference" tab + let sdkTab = docs.navigation.tabs.find(tab => tab.tab === 'SDK Reference'); + + if (!sdkTab) { + console.log("Could not find 'SDK Reference' tab in docs.json. Creating it..."); + sdkTab = { + tab: 'SDK Reference', + groups: [] + }; + } + + // Update the groups + const newGroups = []; + + if(sdkFiles.functions.length > 0) { + newGroups.push({ + group: categoryMap.functions ? categoryMap.functions : 'functions', + pages: sdkFiles.functions.map(file => `sdk-docs/functions/${file}`) + }); + } + + if(sdkFiles.interfaces.length > 0) { + newGroups.push({ + group: categoryMap.interfaces ? categoryMap.interfaces : 'interfaces', + pages: sdkFiles.interfaces.map(file => `sdk-docs/interfaces/${file}`) + }); + } + + if(sdkFiles.classes.length > 0) { + newGroups.push({ + group: categoryMap.classes ? categoryMap.classes : 'classes', + pages: sdkFiles.classes.map(file => `sdk-docs/classes/${file}`) + }); + } + + if(sdkFiles['type-aliases'].length > 0) { + newGroups.push({ + group: categoryMap['type-aliases'] ? categoryMap['type-aliases'] : 'type-aliases', + pages: sdkFiles['type-aliases'].map(file => `sdk-docs/type-aliases/${file}`) + }); + } + + sdkTab.groups = newGroups; + docs.navigation.tabs.push(sdkTab); + + console.debug(`New groups for docs.json: ${JSON.stringify(newGroups, null, 2)}`); + + // Write updated docs.json + console.log(`Writing updated docs.json to ${docsJsonPath}...`); + fs.writeFileSync(docsJsonPath, JSON.stringify(docs, null, 2) + '\n', 'utf8'); + + console.log('Successfully updated docs.json'); + } + +function main() { + const { branch } = parseArgs(); + if (!branch) { + console.error("Error: --branch is required"); + process.exit(1); + } + + console.log(`Branch: ${branch}`); + + if ( + !fs.existsSync(DOCS_SOURCE_PATH) || + !fs.statSync(DOCS_SOURCE_PATH).isDirectory() + ) { + console.error(`Error: docs directory does not exist: ${DOCS_SOURCE_PATH}`); + process.exit(1); + } + + // Create temporary directory + const tempRepoDir = fs.mkdtempSync( + path.join(os.tmpdir(), "mintlify-docs-") + ); + // Clone the repository + console.log(`Cloning repository to ${tempRepoDir}...`); + execSync(`git clone ${TARGET_DOCS_REPO_URL} ${tempRepoDir}`); + + // Check if the specified branch already exists remotely + const branchExists = execSync(`git ls-remote --heads origin ${branch}`, { + cwd: tempRepoDir, + encoding: 'utf8' + }).trim().length > 0; + + if (branchExists) { + console.log(`Branch ${branch} already exists. Checking it out...`); + execSync(`git checkout -b ${branch} origin/${branch}`, { cwd: tempRepoDir }); + } else { + console.log(`Branch ${branch} does not exist. Creating it...`); + execSync(`git checkout -b ${branch}`, { cwd: tempRepoDir }); + } + + // Remove the existing sdk-docs directory + execSync(`rm -rf ${tempRepoDir}/sdk-docs`); + + // Copy the docs directory to the temporary repository + execSync(`cp -r ${DOCS_SOURCE_PATH} ${tempRepoDir}/sdk-docs`); + + // Scan the sdk-docs directory + const sdkDocsDir = path.join(tempRepoDir, "sdk-docs"); + const sdkFiles = scanSdkDocs(sdkDocsDir); + + console.debug(`SDK files: ${JSON.stringify(sdkFiles, null, 2)}`); + + // Update the docs.json file + updateDocsJson(tempRepoDir, sdkFiles); + + // Commit the changes + execSync(`git add docs.json`, { cwd: tempRepoDir }); + execSync(`git add sdk-docs`, { cwd: tempRepoDir }); + execSync(`git commit -m "Auto-updates to SDK Reference Docs"`, { cwd: tempRepoDir }); + execSync(`git push --set-upstream origin ${branch}`, { cwd: tempRepoDir }); + + console.log("Successfully committed and pushed the changes"); +} + +main(); \ No newline at end of file diff --git a/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-content.js b/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-content.js new file mode 100644 index 0000000..a3ca785 --- /dev/null +++ b/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-content.js @@ -0,0 +1,84 @@ +/** + * Content transformation functions (examples, frontmatter, etc.) + */ + +/** + * Convert code examples to Mintlify CodeGroup + */ +export function convertExamplesToCodeGroup(content) { + // Match all consecutive example sections as one group + const exampleSectionRegex = /####\s+(Example|Examples)\s*\n\n([\s\S]*?)(?=\n###\s|\n####\s(?!#)|\n\*\*\*\n\n###|\n\*\*\*$|$)/g; + + return content.replace(exampleSectionRegex, (match, exampleHeading, exampleContent) => { + const codeBlockRegex = /(?:^|\n)(.*?)\n*```(\w+)\n([\s\S]*?)```/g; + const examples = []; + let codeMatch; + + while ((codeMatch = codeBlockRegex.exec(exampleContent)) !== null) { + const descText = codeMatch[1].trim(); + const language = codeMatch[2]; + const code = codeMatch[3].trimEnd(); + + let title; + if (descText && descText.length > 0 && descText.length < 100 && !descText.includes('\n')) { + title = descText.replace(/^[•\-*]\s*/, '').trim(); + } else { + // TODO: Review - Is this what we want for unnamed examples? + title = examples.length === 0 ? 'Example' : `Example ${examples.length + 1}`; + } + + examples.push({ + title: title, + language: language, + code: code + }); + } + + if (examples.length === 0) { + return match; + } + + let codeGroup = '\n\n'; + + for (const example of examples) { + codeGroup += '```' + example.language + ' ' + example.title + '\n'; + codeGroup += example.code + '\n'; + codeGroup += '```\n\n'; + } + + codeGroup += '\n'; + + return codeGroup; + }); +} + +/** + * Add Mintlify frontmatter to the page + */ +export function addMintlifyFrontmatter(content, page) { + const titleMatch = content.match(/^#\s+(.+)$/m); + let title = titleMatch ? titleMatch[1].trim() : ''; + + // Clean up title + title = title.replace(/\*\*/g, '').replace(/`/g, '').trim(); + title = title.replace(/^(?:Interface|Class|Type|Module|Function|Variable|Constant|Enum):\s*/i, '').trim(); + + const escapeYaml = (str) => { + if (str.includes(':') || str.includes('"') || str.includes("'") || str.includes('\n')) { + return str.replace(/"/g, '\\"'); + } + return str; + }; + + const frontmatter = `--- +title: "${escapeYaml(title)}" +--- + +`; + + // Remove the original h1 title (it's now in frontmatter) + let processedContent = content.replace(/^#\s+.+\n\n?/, ''); + + return frontmatter + processedContent; +} + diff --git a/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-linked-types.js b/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-linked-types.js new file mode 100644 index 0000000..d8d9da4 --- /dev/null +++ b/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-linked-types.js @@ -0,0 +1,212 @@ +/** + * Linked type extraction and property parsing functions + */ + +import * as fs from 'fs'; +import * as path from 'path'; + +/** + * Extract properties from a linked type's markdown file + */ +export function extractPropertiesFromLinkedType(linkedTypeInfo, context) { + if (!linkedTypeInfo || !context) { + return []; + } + + const { typePath, typeName } = linkedTypeInfo; + const { currentPagePath, app } = context; + + try { + // Get the output directory from TypeDoc (usually 'docs') + const outputDir = app.options.getValue('out') || 'docs'; + + // Convert relative link to file path + // Links can be: + // - Just the type name: "LoginViaEmailPasswordResponse" + // - Relative path: "../interfaces/LoginViaEmailPasswordResponse" or "./interfaces/LoginViaEmailPasswordResponse" + // - Absolute-looking: "interfaces/LoginViaEmailPasswordResponse" + let filePath; + + // Remove .md or .mdx extension if present + let cleanTypePath = typePath.replace(/\.(md|mdx)$/, ''); + + if (cleanTypePath.startsWith('../') || cleanTypePath.startsWith('./')) { + // Relative path - resolve from current page's directory + const currentDir = path.dirname(path.join(outputDir, currentPagePath || '')); + filePath = path.resolve(currentDir, cleanTypePath); + if (!filePath.endsWith('.md') && !filePath.endsWith('.mdx')) { + filePath += '.md'; + } + } else if (cleanTypePath.includes('/')) { + // Path with directory separator + filePath = path.join(outputDir, cleanTypePath); + if (!filePath.endsWith('.md') && !filePath.endsWith('.mdx')) { + filePath += '.md'; + } + } else { + // Just the type name - try interfaces/ first, then type-aliases/ + // Try .mdx first, then .md + filePath = path.join(outputDir, 'interfaces', cleanTypePath + '.mdx'); + if (!fs.existsSync(filePath)) { + filePath = path.join(outputDir, 'interfaces', cleanTypePath + '.md'); + } + if (!fs.existsSync(filePath)) { + filePath = path.join(outputDir, 'type-aliases', cleanTypePath + '.mdx'); + } + if (!fs.existsSync(filePath)) { + filePath = path.join(outputDir, 'type-aliases', cleanTypePath + '.md'); + } + } + + // Normalize the path + filePath = path.normalize(filePath); + + // Check if file exists + if (!fs.existsSync(filePath)) { + console.warn(`Could not find linked type file: ${filePath} (from typePath: ${typePath})`); + return []; + } + + const content = fs.readFileSync(filePath, 'utf-8'); + return parsePropertiesFromTypeFile(content); + } catch (error) { + console.warn(`Error reading linked type file ${typePath}:`, error.message); + return []; + } +} + +/** + * Parse properties from a type file's markdown content + */ +function parsePropertiesFromTypeFile(content) { + const properties = []; + const lines = content.split('\n'); + + // Find the Properties section + let inPropertiesSection = false; + let i = 0; + + while (i < lines.length) { + const line = lines[i]; + + // Start of Properties section + if (line.match(/^##\s+Properties\s*$/)) { + inPropertiesSection = true; + i++; + continue; + } + + // Stop at next top-level heading (##) + if (inPropertiesSection && line.match(/^##\s+/) && !line.match(/^##\s+Properties\s*$/)) { + break; + } + + // Parse property: ### propertyName or ### propertyName? + if (inPropertiesSection && line.match(/^###\s+/)) { + const propMatch = line.match(/^###\s+(.+)$/); + if (propMatch) { + const rawName = propMatch[1].trim(); + const optional = rawName.endsWith('?'); + // Unescape markdown escapes (e.g., access\_token -> access_token) + let name = optional ? rawName.slice(0, -1).trim() : rawName.trim(); + name = name.replace(/\\_/g, '_').replace(/\\\*/g, '*').replace(/\\`/g, '`'); + + i++; + // Skip blank lines + while (i < lines.length && lines[i].trim() === '') { + i++; + } + + // Get type from next line: > **name**: `type` or > `optional` **name**: `type` + let type = 'any'; + if (i < lines.length && lines[i].includes('`')) { + const typeMatch = lines[i].match(/`([^`]+)`/); + if (typeMatch) { + type = typeMatch[1].trim(); + } + i++; + } + + // Skip blank lines + while (i < lines.length && lines[i].trim() === '') { + i++; + } + + // Collect description and nested properties + const descriptionLines = []; + const nested = []; + + // Look for nested properties (#### heading) + while (i < lines.length) { + const nextLine = lines[i]; + // Stop at next property (###) or section end (## or ***) + if (nextLine.match(/^###\s+/) || nextLine.match(/^##\s+/) || nextLine === '***') { + break; + } + + // Check for nested property (####) + if (nextLine.match(/^####\s+/)) { + const nestedMatch = nextLine.match(/^####\s+(.+)$/); + if (nestedMatch) { + const nestedRawName = nestedMatch[1].trim(); + const nestedOptional = nestedRawName.endsWith('?'); + // Unescape markdown escapes + let nestedName = nestedOptional ? nestedRawName.slice(0, -1).trim() : nestedRawName.trim(); + nestedName = nestedName.replace(/\\_/g, '_').replace(/\\\*/g, '*').replace(/\\`/g, '`'); + + i++; + while (i < lines.length && lines[i].trim() === '') { + i++; + } + + let nestedType = 'any'; + if (i < lines.length && lines[i].includes('`')) { + const nestedTypeMatch = lines[i].match(/`([^`]+)`/); + if (nestedTypeMatch) { + nestedType = nestedTypeMatch[1].trim(); + } + i++; + } + + while (i < lines.length && lines[i].trim() === '') { + i++; + } + + const nestedDescLines = []; + while (i < lines.length && !lines[i].match(/^####\s+/) && !lines[i].match(/^###\s+/) && + !lines[i].match(/^##\s+/) && lines[i] !== '***') { + nestedDescLines.push(lines[i]); + i++; + } + + nested.push({ + name: nestedName, + type: nestedType, + description: nestedDescLines.join('\n').trim(), + optional: nestedOptional + }); + continue; + } + } + + descriptionLines.push(nextLine); + i++; + } + + properties.push({ + name, + type, + description: descriptionLines.join('\n').trim(), + optional, + nested + }); + continue; + } + } + + i++; + } + + return properties; +} + diff --git a/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-parameters.js b/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-parameters.js new file mode 100644 index 0000000..da1d8ca --- /dev/null +++ b/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-parameters.js @@ -0,0 +1,260 @@ +/** + * Parameter conversion functions for TypeDoc Mintlify plugin + */ + +import { escapeAttribute } from './typedoc-mintlify-utils.js'; + +/** + * Convert top-level function parameters (## Parameters with ### param names) + */ +export function convertFunctionParameters(content) { + // Split content by ## headings to isolate the Parameters section + const sections = content.split(/\n(?=##\s+\w)/); + + return sections.map(section => { + // Only process ## Parameters sections (must start with exactly ##, not ###) + if (!section.match(/^##\s+Parameters\s*$/m)) { + return section; + } + + // Extract the content after "## Parameters" + const lines = section.split('\n'); + const paramStartIdx = lines.findIndex(l => l.match(/^##\s+Parameters\s*$/)); + + if (paramStartIdx === -1) return section; + + // Get everything after "## Parameters" line + const paramLines = lines.slice(paramStartIdx + 1); + const paramContent = paramLines.join('\n'); + + // Parse parameters + const params = parseParameters(paramContent, '###', '####'); + + if (params.length === 0) return section; + + // Rebuild section with ParamFields + const beforeParams = lines.slice(0, paramStartIdx + 1).join('\n'); + return beforeParams + '\n\n' + buildParamFieldsSection(params); + }).join('\n'); +} + +/** + * Convert interface method parameters (#### Parameters with ##### param names) + */ +export function convertInterfaceMethodParameters(content) { + return rewriteParameterSections(content, '#### Parameters', '#####', '######'); +} + +/** + * Convert class method parameters (#### Parameters with ##### param names) + */ +export function convertClassMethodParameters(content) { + return rewriteParameterSections(content, '#### Parameters', '#####', '######'); +} + +function rewriteParameterSections(content, sectionHeading, paramLevel, nestedLevel) { + const lines = content.split('\n'); + const result = []; + let i = 0; + + const isTerminatorLine = (line) => { + return ( + line.startsWith('#### Returns') || + line.startsWith('#### Example') || + line === '***' || + line.startsWith('### ') || + line.startsWith('## ') + ); + }; + + while (i < lines.length) { + const line = lines[i]; + if (line.startsWith(sectionHeading)) { + result.push(line); + i++; + const sectionStart = i; + while (i < lines.length && !isTerminatorLine(lines[i])) { + i++; + } + const sectionContentLines = lines.slice(sectionStart, i); + const sectionContent = sectionContentLines.join('\n').trim(); + const params = parseParameters(sectionContent, paramLevel, nestedLevel); + if (params.length > 0) { + const block = buildParamFieldsSection(params).trim(); + if (block) { + result.push(''); + result.push(...block.split('\n')); + result.push(''); + } + } else { + result.push(...sectionContentLines); + } + continue; + } + + result.push(line); + i++; + } + + return result.join('\n'); +} + +/** + * Parse parameters from markdown content + */ +function parseParameters(paramContent, paramLevel, nestedLevel) { + const lines = paramContent.split('\n'); + const params = []; + + const isParamHeading = (line) => line.startsWith(paramLevel + ' '); + const isNestedHeading = nestedLevel ? (line) => line.startsWith(nestedLevel + ' ') : () => false; + const isTerminator = (line) => { + const trimmed = line.trim(); + if (!trimmed) return false; + if (trimmed.startsWith('#### Returns') || trimmed.startsWith('#### Example') || trimmed === '***') { + return true; + } + const nestedPrefix = nestedLevel ? nestedLevel + ' ' : null; + if (/^#{1,3}\s+/.test(trimmed)) { + if (!trimmed.startsWith(paramLevel + ' ') && !(nestedPrefix && trimmed.startsWith(nestedPrefix))) { + return true; + } + } + return false; + }; + + const extractType = (line) => { + if (!line) return null; + const trimmed = line.trim(); + if (!trimmed.startsWith('`')) return null; + return trimmed.replace(/`/g, '').trim(); + }; + + let i = 0; + while (i < lines.length) { + const line = lines[i]; + if (!isParamHeading(line)) { + i++; + continue; + } + + let rawName = line.slice(paramLevel.length).trim(); + const optional = rawName.endsWith('?'); + const cleanName = optional ? rawName.slice(0, -1).trim() : rawName.trim(); + i++; + + // Skip blank lines + while (i < lines.length && lines[i].trim() === '') { + i++; + } + + let type = 'any'; + if (i < lines.length) { + const maybeType = extractType(lines[i]); + if (maybeType) { + type = maybeType; + i++; + } + } + + // Skip blank lines after type + while (i < lines.length && lines[i].trim() === '') { + i++; + } + + const descriptionLines = []; + while (i < lines.length && !isParamHeading(lines[i]) && !isNestedHeading(lines[i]) && !isTerminator(lines[i])) { + descriptionLines.push(lines[i]); + i++; + } + + const nested = []; + while (i < lines.length && isNestedHeading(lines[i])) { + let nestedRawName = lines[i].slice(nestedLevel.length).trim(); + const nestedOptional = nestedRawName.endsWith('?'); + const nestedName = nestedOptional ? nestedRawName.slice(0, -1).trim() : nestedRawName.trim(); + i++; + + while (i < lines.length && lines[i].trim() === '') { + i++; + } + + let nestedType = 'any'; + if (i < lines.length) { + const maybeNestedType = extractType(lines[i]); + if (maybeNestedType) { + nestedType = maybeNestedType; + i++; + } + } + + while (i < lines.length && lines[i].trim() === '') { + i++; + } + + const nestedDescLines = []; + while (i < lines.length && !isNestedHeading(lines[i]) && !isParamHeading(lines[i]) && !isTerminator(lines[i])) { + nestedDescLines.push(lines[i]); + i++; + } + + nested.push({ + name: nestedName, + type: nestedType, + description: nestedDescLines.join('\n').trim(), + optional: nestedOptional + }); + } + + params.push({ + name: cleanName, + type: nested.length > 0 ? 'object' : type, + description: descriptionLines.join('\n').trim(), + optional, + nested + }); + } + + return params; +} + +/** + * Build ParamField components from parsed parameters + */ +function buildParamFieldsSection(params) { + let output = ''; + + for (const param of params) { + const requiredAttr = param.optional ? '' : ' required'; + output += `\n`; + + // Always show description in ParamField if it exists + if (param.description) { + output += `\n${param.description}\n`; + } + + output += '\n\n'; + + // If param has nested fields, wrap them in an Accordion + if (param.nested.length > 0) { + // Accordion title is always "Properties" + output += `\n\n\n`; + + for (const nested of param.nested) { + const optionalLabel = nested.optional ? ' *(optional)*' : ''; + output += `- **${nested.name}**${optionalLabel}: \`${nested.type}\``; + if (nested.description) { + output += ` - ${nested.description}`; + } + output += '\n'; + } + + output += '\n\n\n'; + } else { + output += '\n'; + } + } + + return output; +} + diff --git a/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-plugin.js b/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-plugin.js new file mode 100644 index 0000000..f132622 --- /dev/null +++ b/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-plugin.js @@ -0,0 +1,161 @@ +/** + * TypeDoc plugin for Mintlify MDX output + * Hooks into TypeDoc's markdown renderer to customize output for Mintlify + */ + +import { MarkdownPageEvent } from 'typedoc-plugin-markdown'; +import { ReflectionKind } from 'typedoc'; +import * as fs from 'fs'; +import * as path from 'path'; +import { convertFunctionParameters, convertInterfaceMethodParameters, convertClassMethodParameters } from './typedoc-mintlify-parameters.js'; +import { convertFunctionReturns, convertInterfaceMethodReturns, convertClassMethodReturns } from './typedoc-mintlify-returns.js'; + +/** + * Plugin load function called by TypeDoc + */ +// Track interfaces that are linked types and should be suppressed +const linkedTypeNames = new Set(); + +/** + * Load function called by TypeDoc + */ +export function load(app) { + console.log('Loading Mintlify TypeDoc plugin...'); + + app.renderer.on(MarkdownPageEvent.END, (page) => { + if (!page.contents) return; + + let content = page.contents; + + // Determine what kind of page this is. + const isFunction = page.model?.kind === ReflectionKind.Function; + const isClass = page.model?.kind === ReflectionKind.Class; + const isInterface = page.model?.kind === ReflectionKind.Interface; + + + // 1. Remove breadcrumbs navigation + content = content.replace(/^\[.*?\]\(.*?\)\s*\n+/m, ''); + + // 2. Convert parameters to ParamField components and returns to ResponseField components + // Functions: ## Parameters/Returns with ### field names + // Interface methods: #### Parameters/Returns with ##### field names + // Class methods: #### Parameters/Returns with ##### field names + if (isFunction) { + content = convertFunctionParameters(content); + content = convertFunctionReturns(content, app, page); + } else if (isInterface) { + content = convertInterfaceMethodParameters(content); + content = convertInterfaceMethodReturns(content, app, page, linkedTypeNames, writeLinkedTypesFile); + } else if (isClass) { + content = convertClassMethodParameters(content); + content = convertClassMethodReturns(content, app, page, linkedTypeNames, writeLinkedTypesFile); + } + + // 3. Convert code examples to CodeGroup + content = convertExamplesToCodeGroup(content); + + // 4. Remove links from function signatures (convert [`TypeName`](link) to `TypeName`) + content = content.replace(/\[`([^`]+)`\]\([^)]+\)/g, '`$1`'); + + // 5. Remove .md and .mdx extensions from links + content = content.replace(/\[([^\]]+)\]\(([^)]+)\.mdx?\)/g, '[$1]($2)'); + + // 6. Add frontmatter + content = addMintlifyFrontmatter(content, page); + + page.contents = content; + }); +} + +/** + * Write linked types file + */ +function writeLinkedTypesFile(app) { + const outputDir = app.options.getValue('out') || 'docs'; + const linkedTypesFile = path.join(outputDir, '.linked-types.json'); + try { + fs.writeFileSync(linkedTypesFile, JSON.stringify(Array.from(linkedTypeNames)), 'utf-8'); + } catch (e) { + // Ignore errors writing the file + } +} + +/** + * Convert code examples to Mintlify CodeGroup + */ +function convertExamplesToCodeGroup(content) { + // Match all consecutive example sections as one group + const exampleSectionRegex = /####\s+(Example|Examples)\s*\n\n([\s\S]*?)(?=\n###\s|\n####\s(?!#)|\n\*\*\*\n\n###|\n\*\*\*$|$)/g; + + return content.replace(exampleSectionRegex, (match, exampleHeading, exampleContent) => { + const codeBlockRegex = /(?:^|\n)(.*?)\n*```(\w+)\n([\s\S]*?)```/g; + const examples = []; + let codeMatch; + + while ((codeMatch = codeBlockRegex.exec(exampleContent)) !== null) { + const descText = codeMatch[1].trim(); + const language = codeMatch[2]; + const code = codeMatch[3].trimEnd(); + + let title; + if (descText && descText.length > 0 && descText.length < 100 && !descText.includes('\n')) { + title = descText.replace(/^[•\-*]\s*/, '').trim(); + } else { + title = examples.length === 0 ? 'Example' : `Example ${examples.length + 1}`; + } + + examples.push({ + title: title, + language: language, + code: code + }); + } + + if (examples.length === 0) { + return match; + } + + let codeGroup = '\n\n'; + + for (const example of examples) { + codeGroup += '```' + example.language + ' ' + example.title + '\n'; + codeGroup += example.code + '\n'; + codeGroup += '```\n\n'; + } + + codeGroup += '\n'; + + return codeGroup; + }); +} + +/** + * Add Mintlify frontmatter to the page + */ +function addMintlifyFrontmatter(content, page) { + const titleMatch = content.match(/^#\s+(.+)$/m); + let title = titleMatch ? titleMatch[1].trim() : page.model?.name || 'Documentation'; + + // Clean up title + title = title.replace(/\*\*/g, '').replace(/`/g, '').trim(); + title = title.replace(/^(?:Interface|Class|Type|Module|Function|Variable|Constant|Enum):\s*/i, '').trim(); + + const escapeYaml = (str) => { + if (str.includes(':') || str.includes('"') || str.includes("'") || str.includes('\n')) { + return str.replace(/"/g, '\\"'); + } + return str; + }; + + const frontmatter = `--- +title: "${escapeYaml(title)}" +--- + +`; + + // Remove the original h1 title (it's now in frontmatter) + let processedContent = content.replace(/^#\s+.+\n\n?/, ''); + + return frontmatter + processedContent; +} + diff --git a/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-returns.js b/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-returns.js new file mode 100644 index 0000000..d3ef58c --- /dev/null +++ b/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-returns.js @@ -0,0 +1,614 @@ +/** + * Return/Response field conversion functions for TypeDoc Mintlify plugin + */ + +import * as fs from 'fs'; +import * as path from 'path'; +import { extractPropertiesFromLinkedType } from './typedoc-mintlify-linked-types.js'; +import { escapeAttribute } from './typedoc-mintlify-utils.js'; + +/** + * Extract signature information from content lines + */ +/** + * Try to resolve a type name to a documentation file path + */ +function resolveTypePath(typeName, app, currentPagePath = null) { + // Skip primitive types + const primitives = ['any', 'string', 'number', 'boolean', 'void', 'null', 'undefined', 'object', 'Array', 'Promise']; + if (primitives.includes(typeName)) { + return null; + } + + const outputDir = app.options.getValue('out') || 'docs'; + + // Try interfaces/ first, then type-aliases/ + let filePath = path.join(outputDir, 'interfaces', typeName + '.mdx'); + if (!fs.existsSync(filePath)) { + filePath = path.join(outputDir, 'interfaces', typeName + '.md'); + } + if (!fs.existsSync(filePath)) { + filePath = path.join(outputDir, 'type-aliases', typeName + '.mdx'); + } + if (!fs.existsSync(filePath)) { + filePath = path.join(outputDir, 'type-aliases', typeName + '.md'); + } + + if (fs.existsSync(filePath)) { + // Convert to relative path from current page if possible + if (currentPagePath) { + const currentDir = path.dirname(path.join(outputDir, currentPagePath)); + const relativePath = path.relative(currentDir, filePath).replace(/\\/g, '/'); + return relativePath.startsWith('.') ? relativePath : './' + relativePath; + } + // Otherwise return path relative to outputDir + return path.relative(outputDir, filePath).replace(/\\/g, '/'); + } + + return null; +} + +export function extractSignatureInfo(lines, linkedTypeNames, writeLinkedTypesFile, app, currentPagePath = null) { + const signatureMap = new Map(); + const linkedTypeMap = new Map(); + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + // Match function signature: > **methodName**(...): `returnType` or `returnType`\<`generic`\> + // Handle both simple types and generic types like `Promise`\<`any`\> or `Promise`\<[`TypeName`](link)\> + const sigMatch = line.match(/^>\s*\*\*(\w+)\*\*\([^)]*\):\s*`([^`]+)`(?:\\<(.+?)\\>)?/); + if (sigMatch) { + const methodName = sigMatch[1]; + let returnType = sigMatch[2]; + const genericParam = sigMatch[3]; + + // Check if generic parameter is a markdown link: [`TypeName`](link) + if (genericParam) { + const linkMatch = genericParam.match(/\[`([^`]+)`\]\(([^)]+)\)/); + if (linkMatch) { + const linkedTypeName = linkMatch[1]; + const linkedTypePath = linkMatch[2]; + returnType = `${returnType}<${linkedTypeName}>`; + linkedTypeMap.set(i, { typeName: linkedTypeName, typePath: linkedTypePath }); + // Track this type name so we can suppress its documentation page + linkedTypeNames.add(linkedTypeName); + // Write the file immediately so post-processing script can use it + writeLinkedTypesFile(app); + } else { + // Simple generic type without link - try to resolve it + const simpleGeneric = genericParam.replace(/`/g, '').trim(); + returnType = `${returnType}<${simpleGeneric}>`; + + // Try to resolve the type to a documentation file + const typePath = resolveTypePath(simpleGeneric, app, currentPagePath); + if (typePath) { + linkedTypeMap.set(i, { typeName: simpleGeneric, typePath: typePath }); + linkedTypeNames.add(simpleGeneric); + writeLinkedTypesFile(app); + } + } + } + // Store the return type with the signature line index as the key + signatureMap.set(i, returnType); + } + } + + return { signatureMap, linkedTypeMap }; +} + +/** + * Convert function returns + */ +export function convertFunctionReturns(content, app, page) { + return rewriteReturnSections(content, { + heading: '## Returns', + fieldHeading: '###', + nestedHeading: '####', + stopOnLevel3: false, + app, + page, + }); +} + +/** + * Convert interface method returns + */ +export function convertInterfaceMethodReturns(content, app, page, linkedTypeNames, writeLinkedTypesFile) { + const lines = content.split('\n'); + const { signatureMap, linkedTypeMap } = extractSignatureInfo(lines, linkedTypeNames, writeLinkedTypesFile, app, page?.url); + + return rewriteReturnSections(content, { + heading: '#### Returns', + fieldHeading: '#####', + nestedHeading: '######', + stopOnLevel3: true, + signatureMap, + linkedTypeMap, + app, + page, + }); +} + +/** + * Convert class method returns + */ +export function convertClassMethodReturns(content, app, page, linkedTypeNames, writeLinkedTypesFile) { + const lines = content.split('\n'); + const { signatureMap, linkedTypeMap } = extractSignatureInfo(lines, linkedTypeNames, writeLinkedTypesFile, app, page?.url); + + return rewriteReturnSections(content, { + heading: '#### Returns', + fieldHeading: '#####', + nestedHeading: '######', + stopOnLevel3: true, + signatureMap, + linkedTypeMap, + app, + page, + }); +} + +function rewriteReturnSections(content, options) { + const { + heading, + fieldHeading, + nestedHeading, + stopOnLevel3, + signatureMap = new Map(), + linkedTypeMap = new Map(), + app, + page + } = options; + const lines = content.split('\n'); + const result = []; + let i = 0; + + const isTerminatorLine = (line) => { + const trimmed = line.trim(); + if (!trimmed) return false; + if (trimmed.startsWith('#### Example') || trimmed === '***') { + return true; + } + if (heading !== '## Returns' && trimmed.startsWith('## ')) { + return true; + } + // For function Returns, stop at nested method definitions (#### methodName()) + if (heading === '## Returns' && trimmed.match(/^####\s+\w+\.?\w*\(\)/)) { + return true; + } + if (stopOnLevel3 && trimmed.startsWith('### ')) { + return true; + } + return false; + }; + + while (i < lines.length) { + const line = lines[i]; + if (line.startsWith(heading)) { + result.push(line); + i++; + const sectionStart = i; + while (i < lines.length && !isTerminatorLine(lines[i])) { + i++; + } + const sectionLines = lines.slice(sectionStart, i); + const sectionContent = sectionLines.join('\n').trim(); + + // For function Returns sections, parse nested fields (### headings) + if (heading === '## Returns') { + const { fields, leadingText } = parseReturnFields(sectionContent, fieldHeading, nestedHeading, null, null, null); + if (fields.length === 0) { + result.push(...sectionLines); + } else { + if (leadingText) { + result.push(''); + result.push(leadingText); + } + const fieldsBlock = buildResponseFieldsSection(fields).trimEnd(); + if (fieldsBlock) { + result.push(''); + result.push(fieldsBlock); + result.push(''); + } + } + continue; + } + + // For interface/class method Returns sections + // The Returns section starts at i-1 (after the heading line) + // Look backwards to find the function signature + let sigLineIdx = i - 2; // Go back past the Returns heading + while (sigLineIdx >= 0 && !lines[sigLineIdx].match(/^>\s*\*\*\w+\*\*\(/)) { + sigLineIdx--; + } + + // If we didn't find it by pattern, try to find it in our signature map + // by checking a few lines before the Returns section + if (sigLineIdx < 0 || !signatureMap.has(sigLineIdx)) { + // Try searching a bit further back (up to 10 lines) + for (let j = i - 2; j >= Math.max(0, i - 12); j--) { + if (signatureMap.has(j)) { + sigLineIdx = j; + break; + } + } + } + + const returnTypeFromSignature = sigLineIdx >= 0 ? signatureMap.get(sigLineIdx) : null; + const linkedTypeInfo = sigLineIdx >= 0 ? linkedTypeMap.get(sigLineIdx) : null; + const { fields, leadingText } = parseReturnFields( + sectionContent, + fieldHeading, + nestedHeading, + returnTypeFromSignature, + linkedTypeInfo, + { app, page, currentPagePath: page.url } + ); + if (fields.length === 0) { + result.push(...sectionLines); + } else { + if (leadingText) { + result.push(''); + result.push(leadingText); + } + const fieldsBlock = buildResponseFieldsSection(fields).trimEnd(); + if (fieldsBlock) { + result.push(''); + result.push(fieldsBlock); + result.push(''); + } + } + continue; + } + + result.push(line); + i++; + } + + return result.join('\n'); +} + +function parseReturnFields(sectionContent, fieldHeading, nestedHeading, returnTypeFromSignature = null, linkedTypeInfo = null, context = null) { + if (!sectionContent) { + // If we have a linked type but no section content, try to extract from the linked type + if (linkedTypeInfo && context) { + const properties = extractPropertiesFromLinkedType(linkedTypeInfo, context); + if (properties.length > 0) { + // Return separate ResponseFields for each property (skip the default "result" field) + const resultFields = []; + + // Add a separate ResponseField for each property + for (const prop of properties) { + resultFields.push({ + name: prop.name, + type: prop.type, + description: prop.description, + optional: prop.optional, + nested: prop.nested || [] + }); + } + + return { + fields: resultFields, + leadingText: '' + }; + } + } + return { fields: [], leadingText: '' }; + } + + const lines = sectionContent.split('\n'); + const fields = []; + const headingPrefix = fieldHeading ? `${fieldHeading} ` : null; + const nestedPrefix = nestedHeading ? `${nestedHeading} ` : null; + + const extractTypeFromLine = (line) => { + if (!line) return null; + const trimmed = line.trim(); + if (!trimmed) return null; + if (trimmed.startsWith('>')) { + // Handle lines like: > **entities**: `object` or > **auth**: [`AuthMethods`](../interfaces/AuthMethods) + const blockMatch = trimmed.match(/^>\s*\*\*([^*]+)\*\*:\s*(.+)$/); + if (blockMatch) { + const typePart = blockMatch[2].replace(/`/g, '').trim(); + // Check if it's a markdown link: [TypeName](link) + const linkMatch = typePart.match(/^\[([^\]]+)\]\(([^)]+)\)$/); + if (linkMatch) { + return { type: linkMatch[1], link: linkMatch[2] }; + } + return { type: typePart, link: null }; + } + } + if (trimmed.includes('`')) { + // Extract type from backticks, could be a link: [`AuthMethods`](../interfaces/AuthMethods) + const typeMatch = trimmed.match(/`([^`]+)`/); + if (typeMatch) { + const typePart = typeMatch[1].trim(); + // Check if there's a link after the backticks + const linkMatch = trimmed.match(/`[^`]+`\s*\[([^\]]+)\]\(([^)]+)\)/); + if (linkMatch) { + return { type: linkMatch[1], link: linkMatch[2] }; + } + // Check if the type itself is a link format + const inlineLinkMatch = typePart.match(/^\[([^\]]+)\]\(([^)]+)\)$/); + if (inlineLinkMatch) { + return { type: inlineLinkMatch[1], link: inlineLinkMatch[2] }; + } + return { type: typePart, link: null }; + } + } + // Check for standalone markdown links + const linkMatch = trimmed.match(/^\[([^\]]+)\]\(([^)]+)\)$/); + if (linkMatch) { + return { type: linkMatch[1], link: linkMatch[2] }; + } + return null; + }; + + const isHeadingLine = (line) => headingPrefix && line.startsWith(headingPrefix); + const isNestedHeadingLine = (line) => nestedPrefix && line.startsWith(nestedPrefix); + + const leadingLines = []; + let index = 0; + if (headingPrefix) { + while (index < lines.length && !isHeadingLine(lines[index])) { + if (lines[index].trim()) { + leadingLines.push(lines[index]); + } + index++; + } + } + + // If no field headings found, treat as simple return + if (!headingPrefix || index >= lines.length) { + let type = returnTypeFromSignature || 'any'; + let typeLink = null; + const descriptionLines = []; + + // Check if there's an existing ResponseField in the content + const responseFieldMatch = sectionContent.match(/]*type="([^"]+)"[^>]*>/); + if (responseFieldMatch) { + // Extract type from existing ResponseField + const existingType = responseFieldMatch[1]; + if (existingType && existingType !== 'any') { + type = existingType; + } + } + + for (const line of lines) { + // Skip ResponseField tags - we'll replace them + if (line.trim().startsWith('') { + continue; + } + const maybeType = extractTypeFromLine(line); + if (maybeType && type === 'any') { + if (typeof maybeType === 'object') { + type = maybeType.type; + typeLink = maybeType.link; + } else { + type = maybeType; + } + continue; + } + if (line.trim() && !line.trim().startsWith('`') && !line.trim().startsWith('<')) { + descriptionLines.push(line); + } + } + let description = descriptionLines.join('\n').trim(); + + // Check if we have a linked type to inline + if (linkedTypeInfo && context) { + const properties = extractPropertiesFromLinkedType(linkedTypeInfo, context); + if (properties.length > 0) { + // Return separate ResponseFields for each property (skip the default "result" field) + const resultFields = []; + + // Add a separate ResponseField for each property + for (const prop of properties) { + resultFields.push({ + name: prop.name, + type: prop.type, + description: prop.description, + optional: prop.optional, + nested: prop.nested || [] + }); + } + + return { + fields: resultFields, + leadingText: '', + }; + } + } + + // Add "See [TypeName](link)" to description if there's a type link + if (typeLink) { + if (description) { + description += '\n\nSee [' + type + '](' + typeLink + ')'; + } else { + description = 'See [' + type + '](' + typeLink + ')'; + } + } + // Use 'result' as default name, or extract from description + let name = 'result'; + if (description) { + // Check if description contains a type hint + const typeHint = description.match(/(\w+)\s+(?:instance|object|value)/i); + if (typeHint) { + name = typeHint[1].toLowerCase(); + } + } + return { + fields: [ + { + name, + type, + description, + optional: false, + nested: [], + }, + ], + leadingText: '', + }; + } + + // Parse fields with headings + while (index < lines.length) { + const headingLine = lines[index]; + if (!isHeadingLine(headingLine)) { + index++; + continue; + } + + let rawName = headingLine.slice(headingPrefix.length).trim(); + const optional = rawName.endsWith('?'); + const name = optional ? rawName.slice(0, -1).trim() : rawName.trim(); + index++; + + while (index < lines.length && lines[index].trim() === '') { + index++; + } + + let type = 'any'; + let typeLink = null; + if (index < lines.length) { + const maybeType = extractTypeFromLine(lines[index]); + if (maybeType) { + if (typeof maybeType === 'object') { + type = maybeType.type; + typeLink = maybeType.link; + } else { + type = maybeType; + } + index++; + } + } + + while (index < lines.length && lines[index].trim() === '') { + index++; + } + + const descriptionLines = []; + const nested = []; + + // Collect description and nested fields + while ( + index < lines.length && + !isHeadingLine(lines[index]) && + !(nestedPrefix && isNestedHeadingLine(lines[index])) + ) { + descriptionLines.push(lines[index]); + index++; + } + + // Parse nested fields if any + while (index < lines.length && isNestedHeadingLine(lines[index])) { + const nestedHeadingLine = lines[index]; + let nestedRawName = nestedHeadingLine.slice(nestedPrefix.length).trim(); + const nestedOptional = nestedRawName.endsWith('?'); + const nestedName = nestedOptional ? nestedRawName.slice(0, -1).trim() : nestedRawName.trim(); + index++; + + while (index < lines.length && lines[index].trim() === '') { + index++; + } + + let nestedType = 'any'; + let nestedTypeLink = null; + if (index < lines.length) { + const maybeNestedType = extractTypeFromLine(lines[index]); + if (maybeNestedType) { + if (typeof maybeNestedType === 'object') { + nestedType = maybeNestedType.type; + nestedTypeLink = maybeNestedType.link; + } else { + nestedType = maybeNestedType; + } + index++; + } + } + + while (index < lines.length && lines[index].trim() === '') { + index++; + } + + const nestedDescLines = []; + while ( + index < lines.length && + !isNestedHeadingLine(lines[index]) && + !isHeadingLine(lines[index]) + ) { + nestedDescLines.push(lines[index]); + index++; + } + + // Add "See [TypeName](link)" to nested description if there's a type link + let nestedDescription = nestedDescLines.join('\n').trim(); + if (nestedTypeLink) { + if (nestedDescription) { + nestedDescription += '\n\nSee [' + nestedType + '](' + nestedTypeLink + ')'; + } else { + nestedDescription = 'See [' + nestedType + '](' + nestedTypeLink + ')'; + } + } + + nested.push({ + name: nestedName, + type: nestedType, + description: nestedDescription, + optional: nestedOptional, + }); + } + + // Add "See [TypeName](link)" to description if there's a type link + let description = descriptionLines.join('\n').trim(); + if (typeLink) { + if (description) { + description += '\n\nSee [' + type + '](' + typeLink + ')'; + } else { + description = 'See [' + type + '](' + typeLink + ')'; + } + } + + fields.push({ + name, + type, + description, + optional, + nested, + }); + } + + return { fields, leadingText: leadingLines.join('\n').trim() }; +} + +function buildResponseFieldsSection(fields) { + let output = ''; + + for (const field of fields) { + const requiredAttr = field.optional ? '' : ' required'; + const defaultAttr = field.default ? ` default="${escapeAttribute(field.default)}"` : ''; + output += `\n`; + + if (field.description) { + output += `\n${field.description}\n`; + } + + if (field.nested && field.nested.length > 0) { + output += '\n'; + for (const nested of field.nested) { + const optionalLabel = nested.optional ? ' *(optional)*' : ''; + output += `- **${nested.name}**${optionalLabel}: \`${nested.type}\``; + if (nested.description) { + output += ` - ${nested.description}`; + } + output += '\n'; + } + } + + output += '\n\n\n'; + } + + return output; +} + diff --git a/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-utils.js b/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-utils.js new file mode 100644 index 0000000..809f3a3 --- /dev/null +++ b/scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-utils.js @@ -0,0 +1,11 @@ +/** + * Utility functions for TypeDoc Mintlify plugin + */ + +/** + * Escape special characters for use in HTML attributes + */ +export function escapeAttribute(value) { + return String(value).replace(/"/g, '"'); +} + diff --git a/typedoc.json b/typedoc.json new file mode 100644 index 0000000..79ddeef --- /dev/null +++ b/typedoc.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://typedoc.org/schema.json", + "entryPoints": ["./src/index.ts"], + "out": "docs/content", + "plugin": [ + "typedoc-plugin-markdown", + "./scripts/mintlify-post-processing/typedoc-plugin/typedoc-mintlify-plugin.js" + ], + "fileExtension": ".mdx", + "excludePrivate": true, + "excludeProtected": true, + "excludeInternal": true, + "excludeExternals": true, + "readme": "none", + "gitRevision": "main", + "sort": ["source-order"], + "kindSortOrder": [ + "Project", + "Module", + "Namespace", + "Enum", + "Class", + "Interface", + "TypeAlias", + "Constructor", + "Property", + "Method", + "Function", + "Accessor", + "Variable" + ], + "entryFileName": "README.mdx", + "outputFileStrategy": "members", + "maxTypeConversionDepth": 2, + "hideBreadcrumbs": true, + "disableSources": true, + "hidePageTitle": true +} + diff --git a/writing-docs.md b/writing-docs.md new file mode 100644 index 0000000..8b770bd --- /dev/null +++ b/writing-docs.md @@ -0,0 +1,24 @@ +# SDK Documentation + +Documentation for the SDK is generated using TypeDoc. The TypeDoc files are post-processed to convert them to Mintlify format. You can preview the output locally, and then push it to the docs repo to delpoy it. + +## Before getting started +* Install the repo dependencies: `npm install` +* Install the Mintlify CLI: `npm i -g mint` + +## Generate docs +Open the terminal in the repo and run `npm run create-docs`. The docs files appear under `/docs/content`. + +## Preview docs locally with Mintlify +1. In the terminal, navigate to the `docs` folder. +1. Run `mint dev`. The docs preview opens in your browser. + +> If you notice that the names appearing for the sections of the docs menu aren't right, you may need to adjust `scripts/mintlify-post-processing/category-map.json`. This file maps the names of the subfolders in `/docs/content` to the desired section names in the reference. + +## Push SDK docs to the Mintlify docs repository + +After generating and reviewing the docs, you can push them to the `base44/mintlify-docs` repo to deploy them. + +1. In the terminal, run `npm run push-docs -- --branch `. If the branch already exists, your changes are added to the ones already on the branch. Otherwise, the script creates a new branch with the chosen name. +1. Open the [docs repo](https://github.com/base44-dev/mintlify-docs) and created a PR for your branch. +1. Preview your docs using the [Mintlify dashboard](https://dashboard.mintlify.com/base44/base44?section=previews). \ No newline at end of file From fc84f1c4fa0d166953b97849de5b072aaa2297c4 Mon Sep 17 00:00:00 2001 From: Adam Friedmann Date: Tue, 18 Nov 2025 21:55:14 +0200 Subject: [PATCH 2/4] changes from review --- .../mintlify-post-processing/push-to-docs-repo.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/scripts/mintlify-post-processing/push-to-docs-repo.js b/scripts/mintlify-post-processing/push-to-docs-repo.js index a9a4d04..272530e 100644 --- a/scripts/mintlify-post-processing/push-to-docs-repo.js +++ b/scripts/mintlify-post-processing/push-to-docs-repo.js @@ -148,10 +148,10 @@ function main() { } // Remove the existing sdk-docs directory - execSync(`rm -rf ${tempRepoDir}/sdk-docs`); + fs.rmSync(path.join(tempRepoDir, "sdk-docs"), { recursive: true, force: true }); // Copy the docs directory to the temporary repository - execSync(`cp -r ${DOCS_SOURCE_PATH} ${tempRepoDir}/sdk-docs`); + fs.cpSync(DOCS_SOURCE_PATH, path.join(tempRepoDir, "sdk-docs"), { recursive: true }); // Scan the sdk-docs directory const sdkDocsDir = path.join(tempRepoDir, "sdk-docs"); @@ -168,6 +168,15 @@ function main() { execSync(`git commit -m "Auto-updates to SDK Reference Docs"`, { cwd: tempRepoDir }); execSync(`git push --set-upstream origin ${branch}`, { cwd: tempRepoDir }); + // Remove the temporary directory + try { + fs.rmSync(tempRepoDir, { recursive: true, force: true }); + } catch (e) { + console.error(`Error: Failed to remove temporary directory: ${tempRepoDir}`); + process.exit(1); + } + + console.log("Successfully committed and pushed the changes"); } From 8fc76cfb2412dd1da3033cae45b5c98cb66c8ba1 Mon Sep 17 00:00:00 2001 From: Adam Friedmann Date: Tue, 18 Nov 2025 21:57:39 +0200 Subject: [PATCH 3/4] adding package lock --- package-lock.json | 251 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 237 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8418a06..e638314 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,8 @@ "dotenv": "^16.3.1", "eslint": "^8.54.0", "nock": "^13.4.0", + "typedoc": "^0.28.14", + "typedoc-plugin-markdown": "^4.9.0", "typescript": "^5.3.2", "vitest": "^1.0.0" } @@ -719,13 +721,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, "node_modules/@eslint/eslintrc/node_modules/globals": { "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", @@ -778,6 +773,20 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@gerrit0/mini-shiki": { + "version": "3.15.0", + "resolved": "https://npm.dev.wixpress.com/api/npm/npm-repos/@gerrit0/mini-shiki/-/mini-shiki-3.15.0.tgz", + "integrity": "sha512-L5IHdZIDa4bG4yJaOzfasOH/o22MCesY0mx+n6VATbaiCtMeR59pdRqYk4bEiQkIHfxsHPNgdi7VJlZb2FhdMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/engine-oniguruma": "^3.15.0", + "@shikijs/langs": "^3.15.0", + "@shikijs/themes": "^3.15.0", + "@shikijs/types": "^3.15.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -1230,6 +1239,55 @@ "win32" ] }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "3.15.0", + "resolved": "https://npm.dev.wixpress.com/api/npm/npm-repos/@shikijs/engine-oniguruma/-/engine-oniguruma-3.15.0.tgz", + "integrity": "sha512-HnqFsV11skAHvOArMZdLBZZApRSYS4LSztk2K3016Y9VCyZISnlYUYsL2hzlS7tPqKHvNqmI5JSUJZprXloMvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.15.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "3.15.0", + "resolved": "https://npm.dev.wixpress.com/api/npm/npm-repos/@shikijs/langs/-/langs-3.15.0.tgz", + "integrity": "sha512-WpRvEFvkVvO65uKYW4Rzxs+IG0gToyM8SARQMtGGsH4GDMNZrr60qdggXrFOsdfOVssG/QQGEl3FnJ3EZ+8w8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.15.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "3.15.0", + "resolved": "https://npm.dev.wixpress.com/api/npm/npm-repos/@shikijs/themes/-/themes-3.15.0.tgz", + "integrity": "sha512-8ow2zWb1IDvCKjYb0KiLNrK4offFdkfNVPXb1OZykpLCzRU6j+efkY+Y7VQjNlNFXonSw+4AOdGYtmqykDbRiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.15.0" + } + }, + "node_modules/@shikijs/types": { + "version": "3.15.0", + "resolved": "https://npm.dev.wixpress.com/api/npm/npm-repos/@shikijs/types/-/types-3.15.0.tgz", + "integrity": "sha512-BnP+y/EQnhihgHy4oIAN+6FFtmfTekwOLsQbRw9hOKwqgNy8Bdsjq8B05oAt/ZgvIWWFrshV71ytOrlPfYjIJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://npm.dev.wixpress.com/api/npm/npm-repos/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "dev": true, + "license": "MIT" + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -1250,6 +1308,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://npm.dev.wixpress.com/api/npm/npm-repos/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/node": { "version": "22.13.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.5.tgz", @@ -1262,6 +1330,13 @@ "undici-types": "~6.20.0" } }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://npm.dev.wixpress.com/api/npm/npm-repos/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", @@ -1595,6 +1670,13 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://npm.dev.wixpress.com/api/npm/npm-repos/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, "node_modules/assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -2028,6 +2110,19 @@ "node": ">=10.0.0" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://npm.dev.wixpress.com/api/npm/npm-repos/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2222,13 +2317,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, "node_modules/eslint/node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -2961,6 +3049,16 @@ "node": ">= 0.8.0" } }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://npm.dev.wixpress.com/api/npm/npm-repos/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/local-pkg": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", @@ -3005,6 +3103,13 @@ "yallist": "^3.0.2" } }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://npm.dev.wixpress.com/api/npm/npm-repos/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true, + "license": "MIT" + }, "node_modules/magic-string": { "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", @@ -3056,6 +3161,24 @@ "node": ">=10" } }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://npm.dev.wixpress.com/api/npm/npm-repos/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -3065,6 +3188,13 @@ "node": ">= 0.4" } }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://npm.dev.wixpress.com/api/npm/npm-repos/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -3450,6 +3580,16 @@ "node": ">=6" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://npm.dev.wixpress.com/api/npm/npm-repos/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -3955,6 +4095,69 @@ "node": ">=4" } }, + "node_modules/typedoc": { + "version": "0.28.14", + "resolved": "https://npm.dev.wixpress.com/api/npm/npm-repos/typedoc/-/typedoc-0.28.14.tgz", + "integrity": "sha512-ftJYPvpVfQvFzpkoSfHLkJybdA/geDJ8BGQt/ZnkkhnBYoYW6lBgPQXu6vqLxO4X75dA55hX8Af847H5KXlEFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@gerrit0/mini-shiki": "^3.12.0", + "lunr": "^2.3.9", + "markdown-it": "^14.1.0", + "minimatch": "^9.0.5", + "yaml": "^2.8.1" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 18", + "pnpm": ">= 10" + }, + "peerDependencies": { + "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x" + } + }, + "node_modules/typedoc-plugin-markdown": { + "version": "4.9.0", + "resolved": "https://npm.dev.wixpress.com/api/npm/npm-repos/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.9.0.tgz", + "integrity": "sha512-9Uu4WR9L7ZBgAl60N/h+jqmPxxvnC9nQAlnnO/OujtG2ubjnKTVUFY1XDhcMY+pCqlX3N2HsQM2QTYZIU9tJuw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "typedoc": "0.28.x" + } + }, + "node_modules/typedoc/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://npm.dev.wixpress.com/api/npm/npm-repos/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/typedoc/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://npm.dev.wixpress.com/api/npm/npm-repos/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/typescript": { "version": "5.7.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", @@ -3969,6 +4172,13 @@ "node": ">=14.17" } }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://npm.dev.wixpress.com/api/npm/npm-repos/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, "node_modules/ufo": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", @@ -4418,6 +4628,19 @@ "dev": true, "license": "ISC" }, + "node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://npm.dev.wixpress.com/api/npm/npm-repos/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", From 4092b17bab6c13874b66362b78f30ce88c3d05a3 Mon Sep 17 00:00:00 2001 From: Adam Friedmann Date: Tue, 18 Nov 2025 22:01:27 +0200 Subject: [PATCH 4/4] further fix from review --- .../push-to-docs-repo.js | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/scripts/mintlify-post-processing/push-to-docs-repo.js b/scripts/mintlify-post-processing/push-to-docs-repo.js index 272530e..9597bd3 100644 --- a/scripts/mintlify-post-processing/push-to-docs-repo.js +++ b/scripts/mintlify-post-processing/push-to-docs-repo.js @@ -125,8 +125,10 @@ function main() { process.exit(1); } + let tempRepoDir; + try{ // Create temporary directory - const tempRepoDir = fs.mkdtempSync( + tempRepoDir = fs.mkdtempSync( path.join(os.tmpdir(), "mintlify-docs-") ); // Clone the repository @@ -168,16 +170,14 @@ function main() { execSync(`git commit -m "Auto-updates to SDK Reference Docs"`, { cwd: tempRepoDir }); execSync(`git push --set-upstream origin ${branch}`, { cwd: tempRepoDir }); - // Remove the temporary directory - try { - fs.rmSync(tempRepoDir, { recursive: true, force: true }); - } catch (e) { - console.error(`Error: Failed to remove temporary directory: ${tempRepoDir}`); - process.exit(1); - } - - console.log("Successfully committed and pushed the changes"); + } catch (e) { + console.error(`Error: Failed to commit and push changes: ${e}`); + process.exit(1); + } finally { + // Remove the temporary directory + fs.rmSync(tempRepoDir, { recursive: true, force: true }); + } } main(); \ No newline at end of file