Last active
October 31, 2025 09:42
-
-
Save bguiz/1fa352408aead87a3a6a0b8d8c7fcf62 to your computer and use it in GitHub Desktop.
converts from gitbook's SUMMARY.md file to mintlify's docs.json file
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env node | |
| const fs = require('node:fs/promises'); | |
| const path = require('node:path'); | |
| const gitbookParsers = require('gitbook-parsers'); | |
| const SUMMARY_PATH = '../.gitbook/SUMMARY.md'; | |
| const DOCS_JSON_PATH = '../.gitbook/docs.json'; | |
| const MV_COMMANDS_PATH = '../scripts/mv-commands.sh'; | |
| const mvCommands = [ | |
| 'mkdir -p redirects/', | |
| ]; | |
| const undoMvCommands = [ | |
| 'rm -rf redirects/*.mdx', | |
| ]; | |
| function processGitbookItem(item) { | |
| let result; | |
| if (item.path) { | |
| // remove file extension if present | |
| let toFilePath; | |
| if (item.path.match(/^https?\:/)) { | |
| // this is a URL, which gitbook supports in the nav menu ... but mintlify does not | |
| // so the workaround is to create an empty MDX file and then have that redirect | |
| const urlSlug = item.path | |
| .replace(/[^A-Za-z0-9]+/g, '_') | |
| .replace(/_$/g, ''); | |
| const redirectFile = `redirects/${urlSlug}.mdx`; | |
| mvCommands.push( | |
| `cat << EOF > ${redirectFile} | |
| --- | |
| title: "${item.title}" | |
| url: "${item.path}" | |
| --- | |
| EOF` | |
| ); | |
| undoMvCommands.push(`rm redirects/${urlSlug}.mdx`); | |
| return `redirects/${urlSlug}`; | |
| } else if (item.path.match(/\/README\.md$/)) { | |
| toFilePath = item.path.replace(/\/README\.md$/, '/index.mdx'); | |
| result = toFilePath.replace(/\.mdx$/, ''); | |
| } else if (item.path.match(/\.md$/)) { | |
| toFilePath = item.path.replace(/\.md$/, '.mdx'); | |
| result = toFilePath.replace(/\.mdx$/, ''); | |
| } | |
| if (toFilePath) { | |
| mvCommands.push(`git mv ${fromFilePath} ${toFilePath}`); | |
| undoMvCommands.push(`git mv ${toFilePath} ${fromFilePath}`); | |
| } | |
| result = result || item.path.replace(/(\/README)?\.(md|mdx)$/, ''); | |
| } | |
| return result; | |
| } | |
| // recursively process the GitBook SUMMARY.md | |
| // and restructures it for Mintlify's docs.json | |
| function processGitbookSummary(items) { | |
| return items.map((item) => { | |
| if (item.title === 'Wallet') { | |
| console.log(item); | |
| } | |
| let mintlifyItem; | |
| const itemResult = processGitbookItem(item); | |
| if (!item.articles || item.articles.length === 0) { | |
| mintlifyItem = itemResult; | |
| } else { | |
| mintlifyItem = { | |
| group: item.title, | |
| pages: [ | |
| itemResult, | |
| ], | |
| }; | |
| mintlifyItem.pages = [ | |
| ...mintlifyItem.pages, | |
| ...(processGitbookSummary(item.articles)), | |
| ]; | |
| } | |
| return mintlifyItem; | |
| }); | |
| } | |
| async function convertSummaryToDocsJson(inputPath, outputPath, mvCommandsOutputPath) { | |
| try { | |
| const summaryContent = await fs.readFile(inputPath, 'utf8'); | |
| // use gitbook-parsers to parse the SUMMARY.md format | |
| const parser = gitbookParsers.getForFile(path.basename(inputPath)); | |
| const gitbookSummary = await parser.summary(summaryContent); | |
| const mintlifyNavigation = processGitbookSummary(gitbookSummary.chapters); | |
| // object to be serialised into Mintlify docs.json | |
| const mintlifyDocs = { | |
| "$schema": "https://mintlify.com/docs.json", | |
| "theme": "mint", | |
| "name": "Injective Docs", | |
| "colors": { | |
| "primary": "#004D9D", | |
| "light": "#29A19C", | |
| "dark": "#0D1821" | |
| }, | |
| "styling": { | |
| "eyebrows": "breadcrumbs" | |
| }, | |
| // "logo": { | |
| // "dark": "/logo/logo-dark.svg", | |
| // "light": "/logo/logo-light.svg" | |
| // }, | |
| navigation: { | |
| groups: [ | |
| { | |
| "group": "Injective", | |
| pages: mintlifyNavigation, | |
| }, | |
| ], | |
| }, | |
| }; | |
| await fs.writeFile(outputPath, JSON.stringify(mintlifyDocs, null, 2), 'utf8'); | |
| console.log(`Successfully converted ${inputPath} to ${outputPath}`); | |
| const mvCommandsScript = '#!/bin/sh\n\n' + mvCommands.join('\n'); | |
| await fs.writeFile(mvCommandsOutputPath, mvCommandsScript, 'utf8'); | |
| console.log(`Wrote mv commands to ${mvCommandsOutputPath}`); | |
| const undoMvCommandsScript = '#!/bin/sh\n\n' + undoMvCommands.join('\n'); | |
| const undoMvCommandsOutputPath = mvCommandsOutputPath.replace(/\.sh$/, '-undo.sh'); | |
| await fs.writeFile(undoMvCommandsOutputPath, undoMvCommandsScript, 'utf8'); | |
| console.log(`Wrote undo mv commands to ${undoMvCommandsOutputPath}`); | |
| } catch (error) { | |
| console.error('Error during conversion:', error); | |
| } | |
| } | |
| // run | |
| convertSummaryToDocsJson(SUMMARY_PATH, DOCS_JSON_PATH, MV_COMMANDS_PATH); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # reformats from gitbook's MD format to mintlify's MDX format | |
| SCRIPTS_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) | |
| echo "SCRIPTS_DIR=${SCRIPTS_DIR}" | |
| export SCRIPTS_DIR | |
| DIR=$( cd -- "${SCRIPTS_DIR}/../.gitbook" &> /dev/null && pwd ) | |
| # DIR=${SCRIPTS_DIR} | |
| # # DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )/../scripts" &> /dev/null && pwd ) | |
| echo "editing syntax for all .mdx files in dir: ${DIR}" | |
| edit_file() { | |
| echo "===processing MDX===" | |
| echo "${1}"; | |
| sed -i '' \ | |
| -e 's/{% hint style="\([a-zA-Z0-9-]*\)" %}/<Callout icon="\1" color="#07C1FF" iconType="regular">/g' \ | |
| -e 's/{% endhint %}/<\/Callout>/g' \ | |
| -e 's/<br>/<br \/>/g' \ | |
| -e 's/{% tabs %}/<Tabs>/g' \ | |
| -e 's/{% endtabs %}/<\/Tabs>/g' \ | |
| -e 's/{% tab title="\(.*\)" %}/<Tab title="\1">/g' \ | |
| -e 's/{% endtab %}/<\/Tab>/g' \ | |
| -e 's/{% embed url="\(.*\)" %}/<iframe src="\1" className="w-full h-96 rounded-xl"><\/iframe>/g' \ | |
| "${1}" | |
| # \[([^\]]+)\]\((?!https?:\/\/)([^\)"]+)\/README\.md\s+("[^"\)]+")\) [$1]($2/ $3) | |
| # \[([^\]]+)\]\((?!https?:\/\/)([^\)]+)([^\)]+)/README\.md\) [$1]($2/) | |
| # \[([^\]]+)\]\((?!https?:\/\/)([^\)]+)\.md\s+("[^"]+")\) [$1]($2/ $3) | |
| # \[([^\]]+)\]\((?!https?:\/\/)([^\)]+)\.md\) [$1]($2/) | |
| # \[([^\]]+)\]\((?!(?:\.\.\/|\.\/|http))([^\)]+)\.md\) [$1](./$2/) | |
| # \[([^\]]+)\]\((?!https?:\/\/)([^\)]+)\.md#([^\)]+)\) [$1]($2/#$3) | |
| # \[([^\]]+)\]\((?!(?:#|\.\.\/|\.\/|http|mailto))([^\)"]+)\) [$1](./$2) | |
| # \[([^\]]+)\]\((?!(?:#|\.\.\/|\.\/|http|mailto))([^\)"]+)\s+("[^"]+")\) [$1](./$2 $3) | |
| # (?:\.\.?\/)*\.gitbook\/assets\/(.*?)\.png /img/$1.png | |
| ${SCRIPTS_DIR}/title-updater.js "${1}" | |
| } | |
| export -f edit_file | |
| find "${DIR}" -iname '*.mdx' -type f -print0 | \ | |
| sort -z -u | \ | |
| xargs -0 -r -n 1 -P 4 bash -c 'edit_file "$@"' _ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env node | |
| const fs = require('node:fs/promises'); | |
| const matter = require('gray-matter'); | |
| async function processMarkdownFile(filePath) { | |
| try { | |
| const fileContent = await fs.readFile(filePath, 'utf8'); | |
| const { data: frontmatter, content: body } = matter(fileContent); | |
| // (3) If it has a `title` property in the front matter, do nothing and exit | |
| if (frontmatter.title) { | |
| console.error(`File '${filePath}' already has a title in its frontmatter. Exiting.`); | |
| return; | |
| } | |
| // Find the first non-empty line to check for a heading | |
| const lines = body.trim().split('\n'); | |
| const firstLineIndex = lines.findIndex(line => line.startsWith('#')); | |
| const firstLine = firstLineIndex !== -1 ? lines[firstLineIndex].trim() : null; | |
| if (!firstLine) { | |
| console.error(`No heading found at the beginning of the body in '${filePath}'. Exiting.`); | |
| return; | |
| } | |
| // (5) if the first heading is anything other than a `h1` | |
| if (firstLine.indexOf('# ') !== 0) { | |
| // (5a) throw an error and exit | |
| throw new Error(`Error in '${filePath}': The first heading is not a h1.`); | |
| } | |
| // (6) if the first heading is a `h1` | |
| // (6a) add the heading to the front matter as a `title` property | |
| const title = firstLine.substring(2).trim(); | |
| frontmatter.title = title; | |
| // (6b) remove the heading from the markdown body | |
| lines.splice(firstLineIndex, 1); | |
| const updatedBody = lines.join('\n'); | |
| // (6c) write updated file to disk, overwriting input file | |
| const updatedFileContent = matter.stringify(updatedBody, frontmatter); | |
| await fs.writeFile(filePath, updatedFileContent); | |
| console.log(`Updated '${filePath}': added title '${title}' to frontmatter and removed h1 from body.`); | |
| } catch (error) { | |
| console.error(error.message); | |
| process.exit(1); | |
| } | |
| } | |
| const inputFile = process.argv[2]; | |
| if (!inputFile) { | |
| console.error('Please provide the path to a markdown file.'); | |
| process.exit(1); | |
| } | |
| processMarkdownFile(inputFile); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment