Skip to content

Instantly share code, notes, and snippets.

@bguiz
Last active October 31, 2025 09:42
Show Gist options
  • Select an option

  • Save bguiz/1fa352408aead87a3a6a0b8d8c7fcf62 to your computer and use it in GitHub Desktop.

Select an option

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
#!/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);
#!/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 "$@"' _
#!/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