Transform
Functions
transformMintlifyCallouts
function transformMintlifyCallouts(content: string): string
Use transformMintlifyCallouts to convert Mintlify-flavored callout components into a unified <Callout> format that skrypt's documentation renderer understands.
Reach for this when you're migrating existing Mintlify MDX content into a skrypt-generated doc site. Mintlify uses named tags like <Note>, <Warning>, <Tip>, <Info>, and <Check> — skrypt normalizes these into a single <Callout type="..."> component so your doc site renders them consistently without Mintlify as a dependency.
Each Mintlify tag maps to a corresponding type attribute on the output <Callout>. The transformation handles multiline content and processes all callout variants in a single pass over the string.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
content | string | Yes | Raw MDX or HTML string containing Mintlify callout tags. The entire file contents work fine — non-callout content passes through unchanged. |
Returns
Returns the transformed string with all Mintlify callout tags replaced by <Callout type="..."> equivalents. Feed the result directly into your MDX pipeline or write it back to disk. Content between the tags is preserved exactly, including whitespace and nested elements.
Heads up
- Only the five Mintlify callout tags (
Note,Warning,Tip,Info,Check) are transformed — any other custom Mintlify components pass through untouched and will need separate handling. - The replacement is case-sensitive, so
<note>(lowercase) won't match. Mintlify's own convention is PascalCase, so this is only a concern if your source content is inconsistent.
Example:
function transformMintlifyCallouts(content) {
const MINTLIFY_CALLOUT_MAP = {
Note: "note",
Warning: "warning",
Tip: "tip",
Info: "info",
Check: "check",
};
for (const [tag, type] of Object.entries(MINTLIFY_CALLOUT_MAP)) {
const regex = new RegExp(`<${tag}>([\\s\\S]*?)<\\/${tag}>`, "g");
content = content.replace(regex, `<Callout type="${type}">$1</Callout>`);
}
return content;
}
const mintlifyMdx = `
# Authentication
<Note>
You must include your API key in every request header.
</Note>
<Warning>
Never expose your secret key in client-side code.
</Warning>
<Tip>
Use environment variables to manage keys across environments.
</Tip>
Regular paragraph content is left untouched.
`;
try {
const result = transformMintlifyCallouts(mintlifyMdx);
console.log(result);
// # Authentication
//
// <Callout type="note">
// You must include your API key in every request header.
// </Callout>
//
// <Callout type="warning">
// Never expose your secret key in client-side code.
// </Callout>
//
// <Callout type="tip">
// Use environment variables to manage keys across environments.
// </Callout>
//
// Regular paragraph content is left untouched.
} catch (err) {
console.error("Transformation failed:", err.message);
}
transformDocusaurusAdmonitions
function transformDocusaurusAdmonitions(content: string): string
Use transformDocusaurusAdmonitions to convert Docusaurus-style admonition blocks into your doc platform's native callout format during content migration or preprocessing.
Reach for this when you're migrating docs from Docusaurus to another platform (like Fumadocs or Nextra) and need to bulk-convert :::note, :::tip, :::caution, and similar blocks without manually rewriting every file.
It scans the content string for Docusaurus admonition syntax — :::type[Optional Title]\ncontent\n::: — and replaces each match with the equivalent callout component, preserving the type, optional title, and body text.
| Name | Type | Required | Description |
|---|---|---|---|
content | string | Yes | Raw MDX or Markdown file content containing Docusaurus admonition blocks. Supports note, tip, info, caution, danger, and warning types. |
Returns the transformed content string with all admonition blocks replaced. Write the result directly to your output .mdx file or pipe it into additional transformers in your migration pipeline.
Heads up:
- Admonitions must use the exact Docusaurus format — the opening
:::typeand closing:::must each be on their own line, or the regex won't match. - Unrecognized admonition types fall back to
inforather than throwing, so a typo like:::hintwill silently produce an info callout instead of an error.
Example:
const fs = require("fs");
const path = require("path");
// Inline the admonition type map and transformation logic
const DOCUSAURUS_ADMONITION_MAP = {
note: "note",
tip: "tip",
info: "info",
caution: "warning",
danger: "danger",
warning: "warning",
};
function transformDocusaurusAdmonitions(content) {
return content.replace(
/:::(note|tip|info|caution|danger|warning)(?:\[(.+?)\])?\n([\s\S]*?):::/g,
(_match, type, title, body) => {
const calloutType = DOCUSAURUS_ADMONITION_MAP[type] || "info";
const titleAttr = title ? ` title="${title}"` : "";
return `<Callout type="${calloutType}"${titleAttr}>\n${body.trim()}\n</Callout>`;
}
);
}
// Simulate a Docusaurus MDX file being migrated
const docusaurusContent = `
# Authentication
Before making requests, you'll need an API key.
:::note[Where to find your key]
Navigate to **Settings → API Keys** in your dashboard to generate a new key.
:::
:::caution
Never commit your API key to source control. Use environment variables instead.
:::
:::tip
Keys prefixed with \`sk_live_\` are production keys. Use \`sk_test_\` keys during development.
:::
`.trim();
try {
const transformed = transformDocusaurusAdmonitions(docusaurusContent);
// In a real migration pipeline, you'd write this to your output directory:
// fs.writeFileSync(path.join("./content/docs", "authentication.mdx"), transformed, "utf8");
console.log("Transformed content:\n");
console.log(transformed);
} catch (err) {
console.error("Transformation failed:", err.message);
}
// Expected output:
// # Authentication
//
// Before making requests, you'll need an API key.
//
// <Callout type="note" title="Where to find your key">
// Navigate to **Settings → API Keys** in your dashboard to generate a new key.
// </Callout>
//
// <Callout type="warning">
// Never commit your API key to source control. Use environment variables instead.
// </Callout>
//
// <Callout type="tip">
// Keys prefixed with `sk_live_` are production keys. Use `sk_test_` keys during development.
// </Callout>
transformGitBookHints
function transformGitBookHints(content: string): string
Use transformGitBookHints to convert GitBook hint blocks into MDX <Callout> components so your migrated docs render correctly in skrypt's output.
When you're migrating existing GitBook documentation into skrypt, raw {% hint %} tags will appear as broken markup in the generated site. Run your content through this function before writing it to MDX files to replace every hint block with a properly typed <Callout>.
It scans the content string for all {% hint style="..." %}...{% endhint %} blocks — including multiline ones — and replaces each with <Callout type="...">...</Callout>, mapping GitBook's style names (e.g. info, warning, danger) to their callout equivalents.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
content | string | Yes | Raw MDX or Markdown string containing GitBook hint syntax. All hint blocks in the string are transformed in a single pass. |
Returns
Returns the full content string with every GitBook hint block replaced by an MDX <Callout> component. Pass the result directly to your file writer when generating MDX output. Unrecognized style values fall back to type="info" rather than throwing, so the output is always valid.
Heads up
- The function transforms all hint blocks in the string at once — there's no way to target a single block. If you only want to transform part of a document, split it first.
- Whitespace inside the hint body is trimmed, so leading/trailing newlines around the hint content won't appear in the rendered callout.
Example:
// Inline the transformation logic — no imports from autodocs
const GITBOOK_HINT_MAP: Record<string, string> = {
info: "info",
warning: "warning",
danger: "danger",
success: "tip",
};
function transformGitBookHints(content: string): string {
return content.replace(
/\{%\s*hint\s+style="(\w+)"\s*%\}([\s\S]*?)\{%\s*endhint\s*%\}/g,
(_match, style: string, body: string) => {
const calloutType = GITBOOK_HINT_MAP[style] || "info";
return `<Callout type="${calloutType}">${body.trim()}</Callout>`;
}
);
}
// Simulates a raw MDX file pulled from a GitBook export
const rawContent = `
# Authentication
Before making requests, generate an API key in your dashboard.
{% hint style="info" %}
API keys are scoped to a single workspace. Create separate keys for staging and production.
{% endhint %}
{% hint style="warning" %}
Never commit your API key to source control. Use environment variables instead.
{% endhint %}
{% hint style="custom" %}
This hint uses an unrecognized style and will fall back to "info".
{% endhint %}
`;
try {
const transformed = transformGitBookHints(rawContent);
console.log(transformed);
// Expected output:
//
// # Authentication
//
// Before making requests, generate an API key in your dashboard.
//
// <Callout type="info">API keys are scoped to a single workspace. Create separate keys for staging and production.</Callout>
//
// <Callout type="warning">Never commit your API key to source control. Use environment variables instead.</Callout>
//
// <Callout type="info">This hint uses an unrecognized style and will fall back to "info".</Callout>
} catch (err) {
console.error("Transformation failed:", err);
}
transformReadmeCallouts
function transformReadmeCallouts(content: string): string
Use transformReadmeCallouts to convert ReadMe RDMD-flavored callout blocks into standard MDX-compatible callout components, so your imported ReadMe docs render correctly in your generated documentation site.
Reach for this when you're migrating content from ReadMe.com or processing markdown that contains RDMD callout syntax. It fits naturally in a preprocessing step before passing content to skrypt generate, or inside a custom content pipeline that normalizes third-party markdown before rendering.
RDMD callouts use a blockquote-with-emoji convention — for example, > 📘 Note title followed by > body lines — that standard markdown renderers ignore or render as plain blockquotes. This function detects those emoji-prefixed patterns and rewrites them into structured callout components your doc site can style and display correctly.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
content | string | Yes | The raw markdown string containing RDMD callout blocks. Typically the full contents of a .md or .mdx file exported from ReadMe.com. |
Returns
Returns the transformed markdown string with RDMD callout blocks replaced by MDX callout components. Pass the result directly to your file writer or doc pipeline — it's a drop-in replacement for the original content string.
Heads up
- Only blockquotes that begin with a recognized RDMD emoji (e.g. 📘, 🚧, ❗, 👍) are transformed. Plain blockquotes are left untouched.
- Multi-line callout bodies must use the RDMD convention of prefixing every continuation line with
>— lines that break this pattern will be treated as the end of the callout block.
Example:
// Inline types — no import from autodocs
type TransformFn = (content: string) => string;
// Minimal self-contained implementation matching the documented behavior
const README_EMOJI_MAP: Record<string, string> = {
"📘": "info",
"👍": "success",
"🚧": "warning",
"❗": "danger",
};
const transformReadmeCallouts: TransformFn = (content) => {
const emojiPattern = Object.keys(README_EMOJI_MAP).join("|");
const rdmdRegex = new RegExp(
`> (${emojiPattern}) (.+)\\n((?:> .+\\n?)*)`,
"g"
);
return content.replace(rdmdRegex, (_, emoji, title, body) => {
const type = README_EMOJI_MAP[emoji] ?? "info";
const bodyText = body
.split("\n")
.map((line: string) => line.replace(/^> /, "").trim())
.filter(Boolean)
.join("\n");
return `<Callout type="${type}" title="${title}">\n${bodyText}\n</Callout>\n`;
});
};
// Realistic ReadMe export content
const readmeExport = `
# Authentication
All requests require an API key.
> 📘 Where to find your API key
> Log in to dashboard.example.com, then navigate to
> Settings → API Keys to generate a new key.
> 🚧 Rate limits apply
> Free tier accounts are limited to 100 requests per minute.
Pass the key as a Bearer token in the Authorization header.
`;
try {
const transformed = transformReadmeCallouts(readmeExport);
console.log("Transformed content:\n");
console.log(transformed);
// Expected output:
// # Authentication
//
// All requests require an API key.
//
// <Callout type="info" title="Where to find your API key">
// Log in to dashboard.example.com, then navigate to
// Settings → API Keys to generate a new key.
// </Callout>
//
// <Callout type="warning" title="Rate limits apply">
// Free tier accounts are limited to 100 requests per minute.
// </Callout>
//
// Pass the key as a Bearer token in the Authorization header.
} catch (err) {
console.error("Transform failed:", err);
}
transformNotionCallouts
function transformNotionCallouts(content: string): string
Use transformNotionCallouts to convert Notion's callout syntax into a normalized format your documentation pipeline can render consistently.
When you export content from Notion and pipe it through skrypt's generation flow, callouts come out as raw <aside>...</aside> HTML tags or :::callout fenced blocks. This function sanitizes both formats so they render correctly in your MDX output instead of appearing as broken markup.
The function handles two Notion export variants in a single pass: the HTML <aside> block format (produced by Notion's HTML export) and the :::callout directive syntax (produced by some Notion-to-Markdown converters). Emoji prefixes that Notion injects at the start of callout bodies are stripped automatically.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
content | string | Yes | Raw markdown or HTML string from a Notion export — typically the full contents of a page file before it's written to disk |
Returns
The transformed string with all callout variants replaced by normalized markup, ready to pass to your MDX renderer or write directly to your output directory. Chain this with other transform* functions in your content pipeline before writing the final file.
Heads up
- This function processes the entire document in one pass — pass the full page content, not individual blocks, so multi-line callout bodies aren't split across calls.
- Notion sometimes nests callouts inside other elements; deeply nested
<aside>tags may not be fully resolved in a single call — run it twice if your export contains nested callouts.
Example:
// Inline types — no import from autodocs
type TransformFn = (content: string) => string;
// Minimal self-contained implementation matching the real behavior
const transformNotionCallouts: TransformFn = (content) => {
// Handle <aside> blocks (Notion HTML export)
content = content.replace(
/<aside>([\s\S]*?)<\/aside>/g,
(_match, body: string) => {
// Strip leading emoji + optional variation selector
const stripped = body.replace(/^\s*\p{Emoji}\uFE0F?\s*/u, "").trim();
return `:::note\n${stripped}\n:::`;
}
);
// Handle :::callout blocks (Notion markdown export)
content = content.replace(
/:::callout\s*([\s\S]*?):::/g,
(_match, body: string) => {
const stripped = body.replace(/^\s*\p{Emoji}\uFE0F?\s*/u, "").trim();
return `:::note\n${stripped}\n:::`;
}
);
return content;
};
// Realistic Notion export content with both callout formats
const notionPageContent = `
# Getting Started with the Payments API
Before you begin, make sure you have your API keys ready.
<aside>
💡 You can find your API keys at dashboard.example.com/settings/keys. Keep your secret key out of version control.
</aside>
## Authentication
Pass your key in the Authorization header on every request.
:::callout
⚠️ Requests without a valid key return a 401 Unauthorized error. Check that you're using the **secret** key, not the publishable key.
:::
## Making Your First Request
`;
try {
const transformed = transformNotionCallouts(notionPageContent);
console.log(transformed);
// Expected output:
//
// # Getting Started with the Payments API
//
// Before you begin, make sure you have your API keys ready.
//
// :::note
// You can find your API keys at dashboard.example.com/settings/keys. Keep your secret key out of version control.
// :::
//
// ## Authentication
//
// Pass your key in the Authorization header on every request.
//
// :::note
// Requests without a valid key return a 401 Unauthorized error. Check that you're using the **secret** key, not the publishable key.
// :::
//
// ## Making Your First Request
} catch (err) {
console.error("Transform failed:", err);
}
transformConfluenceCallouts
function transformConfluenceCallouts(content: string): string
Use transformConfluenceCallouts to convert Confluence structured macro callouts into MDX-compatible <Callout> components.
Reach for this when you're migrating Confluence documentation into skrypt's MDX pipeline — specifically when your source content contains info, note, warning, or tip macros that need to render correctly in your generated doc site.
It scans the input string for Confluence's verbose <ac:structured-macro> XML syntax, strips the inner HTML tags from the body, and replaces each macro with a clean <Callout type="..."> element that your MDX doc template understands.
| Name | Type | Required | Description |
|---|---|---|---|
content | string | Yes | Raw Confluence page content containing <ac:structured-macro> elements. Typically the full HTML/XML body exported from Confluence. |
Returns the transformed string with all recognized callout macros replaced by <Callout> components. Pass the result directly to your MDX file writer or chain it with other content transformers in your migration pipeline.
Heads up:
- Only
info,note,warning, andtipmacro types are transformed — any otherac:structured-macronames are left untouched. - HTML tags inside the callout body are stripped, so any inline formatting (bold, links, etc.) within the original macro body will be lost.
Example:
function stripHtmlTags(html: string): string {
return html.replace(/<[^>]*>/g, "");
}
function transformConfluenceCallouts(content: string): string {
return content.replace(
/<ac:structured-macro[^>]*ac:name="(info|note|warning|tip)"[^>]*>[\s\S]*?<ac:rich-text-body>([\s\S]*?)<\/ac:rich-text-body>[\s\S]*?<\/ac:structured-macro>/g,
(_match, type: string, body: string) => {
const cleaned = stripHtmlTags(body).trim();
return `<Callout type="${type}">${cleaned}</Callout>`;
}
);
}
const confluencePage = `
<h2>Getting Started</h2>
<p>Follow these steps to set up your environment.</p>
<ac:structured-macro ac:name="info" ac:schema-version="1" ac:macro-id="abc-123">
<ac:rich-text-body>
<p>Make sure you have <strong>Node.js 18+</strong> installed before proceeding.</p>
</ac:rich-text-body>
</ac:structured-macro>
<ac:structured-macro ac:name="warning" ac:schema-version="1" ac:macro-id="def-456">
<ac:rich-text-body>
<p>Running this command in production will restart all active sessions.</p>
</ac:rich-text-body>
</ac:structured-macro>
`;
try {
const mdxContent = transformConfluenceCallouts(confluencePage);
console.log(mdxContent);
// <h2>Getting Started</h2>
// <p>Follow these steps to set up your environment.</p>
//
// <Callout type="info">Make sure you have Node.js 18+ installed before proceeding.</Callout>
//
// <Callout type="warning">Running this command in production will restart all active sessions.</Callout>
} catch (err) {
console.error("Failed to transform callouts:", err);
}
transformMintlifyTabs
function transformMintlifyTabs(content: string): string
Use transformMintlifyTabs to convert Mintlify-flavored <Tabs> MDX syntax into a compatible tab format during documentation migration or cross-platform publishing.
Reach for this when you're migrating docs from Mintlify to another platform (Docusaurus, Nextra, custom MDX) and need to rewrite tab components without manually editing every file. It fits into a transformation pipeline alongside other content transforms — run it over raw MDX strings before writing them to disk.
It scans the input string for Mintlify's <Tabs><Tab title="...">...</Tab></Tabs> pattern and rewrites each match into the target tab syntax, preserving titles and inner content.
| Name | Type | Required | Description |
|---|---|---|---|
content | string | Yes | Raw MDX file content containing Mintlify <Tabs> blocks. Nested or multi-tab structures are handled — pass the full file string, not individual snippets. |
Returns the transformed MDX string with all Mintlify tab syntax rewritten. Pipe the result directly into your file writer or chain it with other transform functions (e.g., transformMintlifyCallouts) before saving.
Heads up:
- Only
<Tab title="...">attributes are parsed — tabs using other attribute names (e.g.,label) will be skipped silently. - The function is pure and stateless: it doesn't read from or write to disk, so you're responsible for reading the source file and writing the output.
Example:
// Inline type to keep example self-contained
type TransformFn = (content: string) => string;
// Inline implementation matching the real behavior
const transformMintlifyTabs: TransformFn = (content) => {
return content.replace(
/<Tabs>([\s\S]*?)<\/Tabs>/g,
(_match, inner) => {
const tabs: { title: string; content: string }[] = [];
const tabRegex = /<Tab\s+title="([^"]+)">([\s\S]*?)<\/Tab>/g;
let m: RegExpExecArray | null;
while ((m = tabRegex.exec(inner)) !== null) {
tabs.push({ title: m[1], content: m[2].trim() });
}
// Rewrite to a generic <TabGroup> format compatible with Nextra/Docusaurus
const tabHeaders = tabs.map((t) => ` <Tab label="${t.title}" />`).join("\n");
const tabPanels = tabs
.map((t) => ` <TabPanel>\n ${t.content}\n </TabPanel>`)
.join("\n");
return `<TabGroup>\n${tabHeaders}\n${tabPanels}\n</TabGroup>`;
}
);
};
// Simulate reading an MDX file that came from a Mintlify project
const mintlifyMdx = `
# Installation
Choose your package manager:
<Tabs>
<Tab title="npm">
\`\`\`bash
npm install acme-sdk
\`\`\`
</Tab>
<Tab title="yarn">
\`\`\`bash
yarn add acme-sdk
\`\`\`
</Tab>
<Tab title="pnpm">
\`\`\`bash
pnpm add acme-sdk
\`\`\`
</Tab>
</Tabs>
`;
try {
const transformed = transformMintlifyTabs(mintlifyMdx);
console.log("Transformed MDX:\n", transformed);
// Expected output: <TabGroup> with three <TabPanel> blocks
// containing the original bash snippets, ready to write to disk
} catch (err) {
console.error("Transform failed:", err);
}
transformDocusaurusTabs
function transformDocusaurusTabs(content: string): string
Use transformDocusaurusTabs to convert Docusaurus <Tabs> / <TabItem> JSX into a format your documentation pipeline can process downstream.
When skrypt generate outputs MDX and your source already contains Docusaurus tab components, this function normalizes them so the rest of the transformation chain — topic grouping, AI enrichment, final MDX rendering — can handle tabbed content without choking on raw JSX.
It scans the content string for <Tabs> blocks, extracts each <TabItem value="..." label="..."> child, and rewrites the structure into a consistent intermediate representation. Nested or multi-tab blocks are all handled in a single pass.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
content | string | Yes | Raw MDX or HTML string containing Docusaurus <Tabs> / <TabItem> markup — typically the file contents read before passing to the doc generator. |
Returns
Returns the transformed string with Docusaurus tab syntax replaced. Feed the result directly into the next step of your content pipeline, or write it to disk as processed MDX.
Heads up
<TabItem>elements without alabelattribute are supported — thevalueattribute is used as the fallback label.- Content outside
<Tabs>blocks is passed through untouched, so it's safe to run this over entire files rather than isolated snippets.
Example:
// Inline implementation — no import from autodocs
function transformDocusaurusTabs(content: string): string {
return content.replace(
/<Tabs[^>]*>([\s\S]*?)<\/Tabs>/g,
(_match, inner: string) => {
const tabs: { value: string; label: string; content: string }[] = [];
const tabRegex =
/<TabItem\s+value="([^"]+)"(?:\s+label="([^"]*)")?>([\s\S]*?)<\/TabItem>/g;
let m: RegExpExecArray | null;
while ((m = tabRegex.exec(inner)) !== null) {
tabs.push({ value: m[1], label: m[2] ?? m[1], content: m[3].trim() });
}
// Emit a simple fenced representation for downstream processing
return tabs
.map((t) => `<!-- tab: ${t.label} -->\n${t.content}\n<!-- /tab -->`)
.join("\n");
}
);
}
const rawMdx = `
# Installation
<Tabs>
<TabItem value="npm" label="npm">
\`\`\`bash
npm install acme-sdk
\`\`\`
</TabItem>
<TabItem value="yarn" label="Yarn">
\`\`\`bash
yarn add acme-sdk
\`\`\`
</TabItem>
<TabItem value="pnpm">
\`\`\`bash
pnpm add acme-sdk
\`\`\`
</TabItem>
</Tabs>
Continue reading below.
`;
try {
const result = transformDocusaurusTabs(rawMdx);
console.log(result);
// Expected output:
//
// # Installation
//
// <!-- tab: npm -->
// ```bash
// npm install acme-sdk
// ```
// <!-- /tab -->
// <!-- tab: Yarn -->
// ```bash
// yarn add acme-sdk
// ```
// <!-- /tab -->
// <!-- tab: pnpm --> ← label falls back to value
// ```bash
// pnpm add acme-sdk
// ```
// <!-- /tab -->
//
// Continue reading below.
} catch (err) {
console.error("Transformation failed:", err);
}
transformGitBookTabs
function transformGitBookTabs(content: string): string
Use transformGitBookTabs to convert GitBook tab syntax into standard Markdown so your documentation renders correctly after migrating away from GitBook.
When you run skrypt generate on a codebase that previously used GitBook, source files often contain GitBook-flavored template tags like {% tabs %} and {% tab title="..." %}. These tags break MDX rendering. Pass your raw file content through this function before writing output to strip those tags and produce clean, portable Markdown.
The function scans the content string for GitBook tab block patterns and replaces each one with a Markdown-compatible equivalent, handling nested tab entries and multiline content within each tab.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
content | string | Yes | Raw documentation content containing GitBook {% tabs %}...{% endtabs %} blocks — typically the full text of a .md or .mdx file read from disk. |
Returns
Returns the transformed content string with all GitBook tab syntax replaced. Feed the result directly into your MDX output pipeline or write it to disk — it's safe to pass through additional transformers in sequence.
Heads up
- Only
{% tabs %}/{% tab %}blocks are handled here. Other GitBook-specific tags (hints, code blocks, etc.) pass through unchanged and will need their own transform step. - The match is whitespace-tolerant (
{% tabs %}works), but tab titles must use double quotes — single-quoted titles like{% tab title='X' %}won't be captured.
Example:
// Inline implementation — self-contained, no imports from autodocs
function transformGitBookTabs(content) {
return content.replace(
/\{%\s*tabs\s*%\}([\s\S]*?)\{%\s*endtabs\s*%\}/g,
(_match, inner) => {
const tabs = [];
const tabRegex =
/\{%\s*tab\s+title="([^"]+)"\s*%\}([\s\S]*?)\{%\s*endtab\s*%\}/g;
let m;
while ((m = tabRegex.exec(inner)) !== null) {
tabs.push({ title: m[1], content: m[2].trim() });
}
return tabs
.map((tab) => `**${tab.title}**\n\n${tab.content}`)
.join("\n\n---\n\n");
}
);
}
const rawGitBookContent = `
# Authentication
Choose your preferred language:
{% tabs %}
{% tab title="TypeScript" %}
\`\`\`ts
const client = new ApiClient({ apiKey: "sk_live_4eC39HqLyjWDarjtT1zdp7dc" });
\`\`\`
{% endtab %}
{% tab title="Python" %}
\`\`\`python
client = ApiClient(api_key="sk_live_4eC39HqLyjWDarjtT1zdp7dc")
\`\`\`
{% endtab %}
{% endtabs %}
`;
try {
const transformed = transformGitBookTabs(rawGitBookContent);
console.log(transformed);
// Expected output:
//
// # Authentication
//
// Choose your preferred language:
//
// **TypeScript**
//
// ```ts
// const client = new ApiClient({ apiKey: "sk_live_4eC39HqLyjWDarjtT1zdp7dc" });
// ```
//
// ---
//
// **Python**
//
// ```python
// client = ApiClient(api_key="sk_live_4eC39HqLyjWDarjtT1zdp7dc")
// ```
} catch (err) {
console.error("Transform failed:", err.message);
}
transformReadmeCodeBlocks
function transformReadmeCodeBlocks(content: string): string
Use transformReadmeCodeBlocks to convert ReadMe.com's proprietary [block:code] syntax into <CodeGroup> MDX components, so your imported ReadMe docs render correctly in skrypt-generated documentation sites.
Reach for this when migrating existing ReadMe documentation into skrypt. ReadMe exports use a [block:code] JSON block format that no standard MDX renderer understands — this function translates those blocks into the <CodeGroup> components that skrypt's doc site template renders natively.
It scans the content string for every [block:code]...[/block] occurrence, parses the embedded JSON, and replaces each match with the equivalent <CodeGroup> MDX. Blocks that contain malformed JSON are left untouched rather than dropped, so a single bad block won't silently swallow content.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
content | string | Yes | Raw markdown or MDX string exported from ReadMe — typically the full contents of a .md file containing one or more [block:code] blocks |
Returns
Returns the transformed string with all valid [block:code] blocks replaced by <CodeGroup> MDX components. Pass the result directly to skrypt's MDX pipeline or write it to a .mdx file in your output directory.
Heads up
- This only handles
[block:code]blocks. Other ReadMe-specific block types (e.g.[block:callout],[block:image]) are passed through unchanged and will need separate transformation. - The replacement is non-destructive: if the JSON inside a block can't be parsed, the original
[block:code]...[/block]text is preserved as-is.
Example:
import fs from "fs";
import path from "path";
// Inline implementation — do not import from autodocs
function transformReadmeCodeBlocks(content: string): string {
return content.replace(
/\[block:code\]\n?([\s\S]*?)\n?\[\/block\]/g,
(_match, jsonStr: string) => {
try {
const data = JSON.parse(jsonStr);
const codes: Array<{ name: string; language: string; code: string }> =
data.codes ?? [];
const tabs = codes
.map(
(block) =>
` <Tab title="${block.name}">\n\`\`\`${block.language}\n${block.code}\n\`\`\`\n </Tab>`
)
.join("\n");
return `<CodeGroup>\n${tabs}\n</CodeGroup>`;
} catch {
return _match;
}
}
);
}
// Simulate a ReadMe export with a [block:code] block
const readmeExport = `
## Authentication
Pass your API key in the Authorization header.
[block:code]
{
"codes": [
{
"name": "TypeScript",
"language": "typescript",
"code": "const res = await fetch('https://api.example.com/v1/users', {\\n headers: { Authorization: 'Bearer sk_live_abc123' }\\n});"
},
{
"name": "Python",
"language": "python",
"code": "import requests\\nres = requests.get('https://api.example.com/v1/users', headers={'Authorization': 'Bearer sk_live_abc123'})"
}
]
}
[/block]
More content follows here.
`;
try {
const transformed = transformReadmeCodeBlocks(readmeExport);
// In a real migration you'd write this to your skrypt output directory:
// fs.writeFileSync(path.join("./content/docs", "authentication.mdx"), transformed);
console.log("Transformed output:\n");
console.log(transformed);
// Expected output:
// ## Authentication
//
// Pass your API key in the Authorization header.
//
// <CodeGroup>
// <Tab title="TypeScript">
// ```typescript
// const res = await fetch('https://api.example.com/v1/users', { ... });
// ```
// </Tab>
// <Tab title="Python">
// ```python
// import requests
// ...
// ```
// </Tab>
// </CodeGroup>
//
// More content follows here.
} catch (err) {
console.error("Transformation failed:", err);
}
transformGitBookSteps
function transformGitBookSteps(content: string): string
Use transformGitBookSteps to convert GitBook's {% stepper %} syntax into standard MDX-compatible markup during a documentation migration.
Reach for this when you're migrating content from GitBook to a skrypt-generated doc site and your source files contain multi-step procedural content. It's typically called as part of a content preprocessing pipeline before passing Markdown to the generator.
The function scans for {% stepper %}...{% endstepper %} blocks, splits the inner content on each {% step %} delimiter, and maps each step into a renderable MDX structure. Malformed or empty step blocks are filtered out automatically.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
content | string | Yes | Raw Markdown or MDX string containing GitBook stepper syntax — the full file contents, not just the block |
Returns
Returns the full content string with all GitBook stepper blocks replaced by MDX-compatible markup. Pass the result directly to skrypt generate as preprocessed input, or write it back to disk before running the generator.
Heads up
- Only
{% stepper %}/{% step %}tags are handled here — other GitBook tags like{% hint %}or{% tabs %}require their own transform functions and won't be touched. - Step content between
{% step %}and the next delimiter is preserved as-is, so any nested GitBook syntax inside a step will remain unconverted.
Example:
// Inline mock of transformGitBookSteps — do not import from autodocs
function transformGitBookSteps(content) {
return content.replace(
/\{%\s*stepper\s*%\}([\s\S]*?)\{%\s*endstepper\s*%\}/g,
(_match, inner) => {
const steps = inner.split(/\{%\s*step\s*%\}/).filter(s => s.trim());
const mapped = steps.map((step, i) => {
const body = step.replace(/\{%\s*endstep\s*%\}/g, "").trim();
return `<Step number={${i + 1}}>\n\n${body}\n\n</Step>`;
});
return `<Steps>\n\n${mapped.join("\n\n")}\n\n</Steps>`;
}
);
}
const gitbookContent = `
# Deploying Your App
Follow these steps to deploy to production.
{% stepper %}
{% step %}
**Install dependencies**
Run \`npm install\` in your project root to pull in all required packages.
{% endstep %}
{% step %}
**Set environment variables**
Copy \`.env.example\` to \`.env\` and fill in your \`DATABASE_URL\` and \`API_SECRET\`.
{% endstep %}
{% step %}
**Run the deploy command**
Execute \`npm run deploy\` — your app will be live at https://yourapp.example.com within 60 seconds.
{% endstep %}
{% endstepper %}
`;
const mdxContent = transformGitBookSteps(gitbookContent);
console.log(mdxContent);
// # Deploying Your App
//
// Follow these steps to deploy to production.
//
// <Steps>
//
// <Step number={1}>
//
// **Install dependencies**
// ...
// </Step>
//
// ...
// </Steps>
transformGitBookExpandable
function transformGitBookExpandable(content: string): string
Use transformGitBookExpandable to convert GitBook expandable blocks into <Accordion> components during a documentation migration.
Reach for this when you're migrating content from GitBook to an MDX-based doc platform (like the one skrypt init scaffolds) and your source files contain {% expandable %} tags that need to render as interactive accordions.
It finds every {% expandable title="..." %}...{% endexpandable %} block in the input string and replaces it with an <Accordion title="..."> JSX element, preserving the inner content. The replacement is global — all expandable blocks in the document are transformed in a single pass.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
content | string | Yes | The raw MDX or Markdown string containing GitBook {% expandable %} tags to transform. Pass the full file contents to handle multiple blocks at once. |
Returns
The transformed string with all {% expandable %} blocks replaced by <Accordion> JSX elements. Pipe the result directly into your MDX file writer, or chain it with other transform* functions before writing.
Heads up
- The title attribute must use double quotes in the source tag (
title="...") — single quotes or unquoted values won't match and the block will be left unchanged. - Inner content is trimmed of leading/trailing whitespace, so blank lines immediately inside the tags won't appear in the rendered output.
Example:
function transformGitBookExpandable(content) {
return content.replace(
/\{%\s*expandable\s+title="([^"]+)"\s*%\}([\s\S]*?)\{%\s*endexpandable\s*%\}/g,
(_match, title, body) => `<Accordion title="${title}">\n${body.trim()}\n</Accordion>`
);
}
const gitbookSource = `
# Authentication
Use API keys to authenticate requests.
{% expandable title="Obtaining your API key" %}
Navigate to **Dashboard → Settings → API Keys** and click **Generate new key**.
Store it in an environment variable — never commit it to source control.
{% endexpandable %}
{% expandable title="Rotating a compromised key" %}
Immediately revoke the key from the Dashboard, then update your environment variables
and redeploy your application.
{% endexpandable %}
`;
const mdxOutput = transformGitBookExpandable(gitbookSource);
console.log(mdxOutput);
// Expected output:
//
// # Authentication
//
// Use API keys to authenticate requests.
//
// <Accordion title="Obtaining your API key">
// Navigate to **Dashboard → Settings → API Keys** and click **Generate new key**.
// Store it in an environment variable — never commit it to source control.
// </Accordion>
//
// <Accordion title="Rotating a compromised key">
// Immediately revoke the key from the Dashboard, then update your environment variables
// and redeploy your application.
// </Accordion>
transformGitBookContentRef
function transformGitBookContentRef(content: string): string
Use transformGitBookContentRef to convert GitBook content-ref block syntax into standard Markdown links during documentation migration or preprocessing.
Reach for this when you're ingesting GitBook-flavored MDX/Markdown into skrypt's pipeline and your source files contain {% content-ref %} blocks that won't render correctly in standard doc platforms. It's typically applied as a preprocessing step before passing content to skrypt generate.
The function strips the full {% content-ref url="..." %}...{% endcontent-ref %} block and replaces it with a [label](url) link, where the label is derived from the filename in the URL (minus the .md extension).
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
content | string | Yes | Raw Markdown or MDX string containing one or more GitBook content-ref blocks to be converted. All matching blocks in the string are replaced in a single pass. |
Returns
Returns the transformed string with all {% content-ref %} blocks replaced by Markdown links. Pass the result directly to your file writer or into the next preprocessing step in your pipeline.
Heads up
- The link label is derived from the last path segment of the URL with
.mdstripped — soguides/getting-started.mdbecomes[getting-started](guides/getting-started.md). If your URLs don't follow a path structure, the label may not be human-readable. - Any content nested between the opening and closing
content-reftags (e.g. a page title hint) is silently discarded — only theurlattribute is preserved.
Example:
function transformGitBookContentRef(content: string): string {
return content.replace(
/\{%\s*content-ref\s+url="([^"]+)"\s*%\}[\s\S]*?\{%\s*endcontent-ref\s*%\}/g,
(_match, url: string) => {
const label = url.replace(/\.md$/, '').split('/').pop() || url
return `[${label}](${url})`
}
)
}
const gitbookSource = `
# Authentication
To get started, see the quickstart guide:
{% content-ref url="guides/quickstart.md" %}
quickstart.md
{% endcontent-ref %}
For advanced configuration:
{% content-ref url="guides/advanced/oauth-setup.md" %}
oauth-setup.md
{% endcontent-ref %}
`
try {
const result = transformGitBookContentRef(gitbookSource)
console.log(result)
// # Authentication
//
// To get started, see the quickstart guide:
//
// [quickstart](guides/quickstart.md)
//
// For advanced configuration:
//
// [oauth-setup](guides/advanced/oauth-setup.md)
} catch (err) {
console.error('Transformation failed:', err)
}
transformGitBookEmbed
function transformGitBookEmbed(content: string): string
Use transformGitBookEmbed to strip GitBook embed tags from content strings, leaving only the raw URL behind.
Reach for this when migrating documentation from GitBook to another platform (like an MDX-based doc site) and your source files contain {% embed url="..." %} syntax that your new renderer doesn't understand.
It replaces every {% embed url="..." %} occurrence in the string with just the URL it wraps — so downstream renderers, link parsers, or your own embed components can handle the URL directly.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
content | string | Yes | The raw markdown or MDX string to transform. All GitBook embed tags found anywhere in the string will be replaced. |
Returns
Returns the transformed string with all {% embed url="..." %} tags replaced by their bare URLs. Feed the result into subsequent transformers or write it directly to your output .mdx files.
Heads up
- Only the
urlattribute is extracted — any other attributes inside the tag (e.g.{% embed url="..." caption="..." %}) are silently dropped along with the tag wrapper.- The replacement is inline: if the embed tag was on its own line, the URL will remain on that line with no surrounding markup. You may want to wrap it in your own embed component syntax afterward.
Example:
function transformGitBookEmbed(content) {
return content.replace(
/\{%\s*embed\s+url="([^"]+)"\s*%\}/g,
(_match, url) => url
);
}
const gitbookPage = `
# Getting Started
Install the SDK using npm or yarn.
{% embed url="https://www.youtube.com/watch?v=dQw4w9WgXcQ" %}
For a deeper dive, check out the architecture overview:
{% embed url="https://docs.example.com/architecture" %}
Then continue to the next section.
`;
try {
const transformed = transformGitBookEmbed(gitbookPage);
console.log(transformed);
// # Getting Started
//
// Install the SDK using npm or yarn.
//
// https://www.youtube.com/watch?v=dQw4w9WgXcQ
//
// For a deeper dive, check out the architecture overview:
//
// https://docs.example.com/architecture
//
// Then continue to the next section.
} catch (err) {
console.error("Transform failed:", err);
}
transformNotionToggles
function transformNotionToggles(content: string): string
Use transformNotionToggles to convert Notion-exported toggle blocks into <Accordion> components compatible with MDX documentation platforms.
When you export content from Notion and pipe it through skrypt's documentation pipeline, toggle blocks come out as raw HTML <details>/<summary> elements. Call this function to upgrade them into <Accordion> components before writing the final MDX output.
It finds every <details><summary>Title</summary>content</details> pattern in the string and replaces it with <Accordion title="Title">content</Accordion>, trimming whitespace from both the title and body in the process.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
content | string | Yes | Raw MDX or HTML string containing Notion-exported toggle blocks — typically the output of a previous transformation step in your doc pipeline. |
Returns
Returns the transformed string with all <details> toggle blocks replaced by <Accordion> components. Pass the result directly to your MDX file writer, or chain it with other transform functions in your pipeline. If no toggle blocks are found, the original string is returned unchanged.
Heads up
- Nested toggles (a
<details>inside another<details>) are processed outside-in — the outer toggle transforms correctly, but the inner one will also be caught in the same pass, so nesting works as expected.- This only matches the exact
<details><summary>structure Notion produces. Hand-authored<details>elements with attributes (e.g.,<details open>) won't match and will be left as-is.
Example:
function transformNotionToggles(content) {
return content.replace(
/<details>\s*<summary>([\s\S]*?)<\/summary>([\s\S]*?)<\/details>/g,
(_match, title, body) => `<Accordion title="${title.trim()}">\n${body.trim()}\n</Accordion>`
)
}
const notionExport = `
# Authentication
Use API keys to authenticate requests.
<details>
<summary>Where do I find my API key?</summary>
Navigate to **Settings → API Keys** in your dashboard.
Click **Generate new key** and copy the value — it won't be shown again.
</details>
<details>
<summary> Rotating keys </summary>
Call \`DELETE /v1/keys/:id\` to revoke an existing key, then generate a new one.
</details>
`.trim()
const mdx = transformNotionToggles(notionExport)
console.log(mdx)
// # Authentication
//
// Use API keys to authenticate requests.
//
// <Accordion title="Where do I find my API key?">
// Navigate to **Settings → API Keys** in your dashboard.
// Click **Generate new key** and copy the value — it won't be shown again.
// </Accordion>
//
// <Accordion title="Rotating keys">
// Call `DELETE /v1/keys/:id` to revoke an existing key, then generate a new one.
// </Accordion>
transformConfluenceHtml
function transformConfluenceHtml(content: string): string
Use transformConfluenceHtml to convert Confluence page HTML — including Atlassian Content (AC) macros — into clean Markdown that skrypt can process and document.
When you're pulling source content from Confluence via the API, the raw HTML comes back packed with proprietary <ac:structured-macro> tags and CDATA blocks that standard HTML-to-Markdown converters don't understand. Reach for this function before passing Confluence content into skrypt's generation pipeline.
It handles the translation layer between Confluence's XML-flavored HTML and standard Markdown — for example, converting <ac:structured-macro ac:name="code"> blocks (including their language hints and CDATA-wrapped bodies) into fenced code blocks.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
content | string | Yes | Raw HTML string from the Confluence REST API — typically the body.storage.value field from a page response |
Returns
A Markdown string with Confluence-specific markup replaced by standard Markdown equivalents. Pass the result directly to skrypt's generate pipeline or write it to a .md / .mdx file for further processing.
Heads up
- This function expects Confluence's storage format HTML (what you get from the REST API), not the rendered HTML from a browser. Passing browser-rendered output will produce unpredictable results.
- Language hints inside code macros are preserved when present, but omitted gracefully when missing — you'll get an unlabeled fenced block rather than an error.
Example:
// Simulate the Confluence REST API response shape
const fetchConfluencePage = async (pageId: string) => {
// In production: GET /wiki/rest/api/content/{pageId}?expand=body.storage
return {
id: pageId,
title: "Authentication Guide",
body: {
storage: {
value: `
<p>Use the following token in all API requests:</p>
<ac:structured-macro ac:name="code" ac:schema-version="1">
<ac:parameter ac:name="language">typescript</ac:parameter>
<ac:plain-text-body><![CDATA[const client = new ApiClient({
token: "sk_live_abc123xyz",
baseUrl: "https://api.example.com/v2",
});]]></ac:plain-text-body>
</ac:structured-macro>
<p>Tokens expire after 90 days.</p>
`.trim(),
},
},
};
};
// Inline implementation — do not import from autodocs
function transformConfluenceHtml(content: string): string {
content = content.replace(
/<ac:structured-macro[^>]*ac:name="code"[^>]*>[\s\S]*?(?:<ac:parameter ac:name="language">([^<]*)<\/ac:parameter>)?[\s\S]*?<ac:plain-text-body><!\[CDATA\[([\s\S]*?)\]\]><\/ac:plain-text-body>[\s\S]*?<\/ac:structured-macro>/g,
(_match: string, lang: string | undefined, code: string) =>
`\`\`\`${lang || ""}\n${code}\n\`\`\``
);
content = content.replace(/<p>([\s\S]*?)<\/p>/g, "$1\n");
return content.trim();
}
const run = async () => {
try {
const page = await fetchConfluencePage("182736450");
const markdown = transformConfluenceHtml(page.body.storage.value);
console.log(`# ${page.title}\n`);
console.log(markdown);
// Expected output:
// # Authentication Guide
//
// Use the following token in all API requests:
//
// ```typescript
// const client = new ApiClient({
// token: "sk_live_abc123xyz",
// baseUrl: "https://api.example.com/v2",
// });
// ```
//
// Tokens expire after 90 days.
} catch (err) {
console.error("Failed to transform Confluence content:", err);
}
};
run();
stripDocusaurusImports
function stripDocusaurusImports(content: string): string
Use stripDocusaurusImports to clean Docusaurus-specific theme imports out of MDX content before rendering it on a non-Docusaurus platform.
When skrypt generates MDX documentation, the output may contain import ... from '@theme/...' statements that are valid in Docusaurus but will break other doc platforms (Nextra, Mintlify, plain React, etc.). Call this function as a post-processing step to make that MDX portable.
It strips any line matching import ... from '@theme/...' using a global multiline regex, removing the import statement and its trailing newline cleanly.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
content | string | Yes | Raw MDX or markdown string to process — typically the full file contents read from skrypt's output directory. |
Returns
The same MDX string with all @theme/ import lines removed. Pipe the result directly into your file writer or further transformation steps — the rest of the MDX structure is untouched.
Heads up
- Only strips
@theme/imports. Other Docusaurus-specific imports (e.g.,@docusaurus/Link) are left in place — you may need additional stripping logic if you're targeting a fully non-Docusaurus environment.
Example:
function stripDocusaurusImports(content) {
return content.replace(/^import\s+.*from\s+['"]@theme\/.*['"];?\s*\n?/gm, '')
}
const rawMdx = `import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';
# createPayment
Use \`createPayment\` to initiate a new payment intent.
\`\`\`ts
const payment = await createPayment({ amount: 4200, currency: 'usd' });
\`\`\`
`
const cleaned = stripDocusaurusImports(rawMdx)
console.log(cleaned)
// # createPayment
//
// Use `createPayment` to initiate a new payment intent.
//
// ```ts
// const payment = await createPayment({ amount: 4200, currency: 'usd' });
//
normalizeFrontmatter
function normalizeFrontmatter(content: string, defaults?: FrontmatterDefaults): string
Use normalizeFrontmatter to ensure MDX files have consistent, Skrypt-compatible frontmatter before writing them to your docs output directory.
Reach for this when you're post-processing generated or hand-written MDX content and need to guarantee required fields (like title, description, or sidebar position) are present and correctly formatted. It's the right tool when merging AI-generated docs with your own metadata defaults.
The function parses any existing frontmatter in the content, merges it with your provided defaults (existing values win), and serializes the result back into a valid MDX string. If the content has no frontmatter and no defaults are provided, it's returned unchanged.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
content | string | Yes | Raw MDX file content, including any existing --- frontmatter block and body text. |
defaults | FrontmatterDefaults | No | Fallback frontmatter values to inject when the content is missing those fields. Existing frontmatter fields take precedence over defaults. |
Returns
Returns the full MDX content as a string with normalized frontmatter prepended. Pass the result directly to fs.writeFile or your MDX pipeline — it's ready to write to disk.
Heads up
- Existing frontmatter fields are never overwritten by
defaults— only missing fields are filled in. If you need to force-override a field, strip it from the content before calling this function. - If
contentcontains no frontmatter block anddefaultsis undefined or empty, the original string is returned as-is with no allocations.
Example:
// Inline types — do not import from autodocs
type FrontmatterDefaults = {
title?: string;
description?: string;
sidebar?: { order?: number; label?: string };
[key: string]: unknown;
};
// Minimal self-contained implementation matching the real function's behavior
function normalizeFrontmatter(content: string, defaults?: FrontmatterDefaults): string {
const fmRegex = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/;
const match = content.match(fmRegex);
const existingData: Record<string, unknown> = {};
let body = content;
if (match) {
body = match[2];
match[1].split("\n").forEach((line) => {
const [key, ...rest] = line.split(": ");
if (key && rest.length) existingData[key.trim()] = rest.join(": ").trim();
});
}
if (!match && !defaults) return content;
const merged = { ...defaults, ...existingData };
const fmLines = Object.entries(merged)
.map(([k, v]) => `${k}: ${typeof v === "object" ? JSON.stringify(v) : v}`)
.join("\n");
return `---\n${fmLines}\n---\n${body}`;
}
// Simulate a generated MDX file that's missing a description and sidebar order
const rawContent = `---
title: Authentication
---
## Overview
Use API keys to authenticate requests to the Acme API.
`;
const defaults: FrontmatterDefaults = {
title: "Untitled",
description: "Auto-generated API reference.",
sidebar: { order: 99 },
};
try {
const normalized = normalizeFrontmatter(rawContent, defaults);
console.log("Normalized MDX content:\n");
console.log(normalized);
// Expected output:
// ---
// title: Authentication ← kept from original, not overwritten
// description: Auto-generated API reference. ← injected from defaults
// sidebar: {"order":99} ← injected from defaults
// ---
//
// ## Overview
// Use API keys to authenticate requests to the Acme API.
} catch (err) {
console.error("Failed to normalize frontmatter:", err);
}
rewriteImagePaths
function rewriteImagePaths(content: string, mapping: Map<string, string>): string
Use rewriteImagePaths to update all image references in generated MDX content after assets have been copied to their final output location.
When skrypt copies images from your source directory to the docs output directory, the paths embedded in generated content still point to the originals. Call this after the asset copy step to sync those references with where the files actually live.
It iterates over every entry in the mapping and performs a global string replacement, so a single image referenced multiple times in the same document will be updated everywhere.
| Name | Type | Required | Description |
|---|---|---|---|
content | string | Yes | The raw MDX string whose image paths need updating — typically the output of a doc generation step before it's written to disk. |
mapping | Map<string, string> | Yes | A map of original source paths to their new output paths, as produced by the asset copy step. Every key found in content will be replaced with its corresponding value. |
Returns the updated MDX string with all image paths replaced. Write this string to your output file in place of the original content.
Heads up:
- Replacements are exact string matches — if your content uses relative paths (
./images/hero.png) but your mapping keys are absolute, nothing will be replaced. Make sure the keys in your mapping match the format used in the generated content.
Example:
// Simulate the asset copy step returning a path mapping
function copyAssets(sourceDir: string, outputDir: string): Map<string, string> {
return new Map([
["./images/hero.png", "/docs/assets/hero.png"],
["./images/architecture-diagram.svg", "/docs/assets/architecture-diagram.svg"],
["./images/quickstart-screenshot.png", "/docs/assets/quickstart-screenshot.png"],
]);
}
function rewriteImagePaths(content: string, mapping: Map<string, string>): string {
for (const [oldPath, newPath] of mapping) {
content = content.replaceAll(oldPath, newPath);
}
return content;
}
const generatedMdx = `
# Getting Started

Follow the quickstart guide below.

## Architecture
The following diagram shows how components interact.

`;
try {
const assetMapping = copyAssets("./src", "./docs");
const finalContent = rewriteImagePaths(generatedMdx, assetMapping);
console.log(finalContent);
// # Getting Started
//
// 
//
// Follow the quickstart guide below.
//
// 
//
// ## Architecture
//
// The following diagram shows how components interact.
//
// 
} catch (err) {
console.error("Failed to rewrite image paths:", err);
}
stripNotionUUIDs
function stripNotionUUIDs(filename: string): string
Use stripNotionUUIDs to clean Notion-exported filenames by removing the 32-character hex IDs Notion appends to every page title.
When you export a Notion workspace, every file gets a UUID suffix like Getting Started a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4.md. Use this function before passing filenames to skrypt generate — or any file processing pipeline — so your generated docs use readable titles instead of Notion's internal identifiers.
It strips any whitespace-separated 32-character lowercase hex sequence from the filename, leaving the extension and human-readable title intact.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
filename | string | Yes | The raw filename from a Notion export, including the UUID suffix and file extension. |
Returns
The cleaned filename string with all UUID suffixes removed. Pass the result directly to your doc generation pipeline or use it to rename files before scanning with skrypt generate.
Heads up
- Only strips suffixes that are exactly 32 hex characters (
[0-9a-f]). Uppercase hex characters (as produced by some export tools) won't be matched — normalize to lowercase first if needed. - Handles nested Notion paths correctly: if a full path string is passed (e.g.,
docs/Getting Started a1b2c3...d4/index.md), UUID segments anywhere in the string will be stripped.
Example:
function stripNotionUUIDs(filename: string): string {
return filename.replace(/\s+[0-9a-f]{32}/g, '')
}
const notionExports = [
"Getting Started a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4.md",
"API Reference 00112233445566778899aabbccddeeff.md",
"Changelog deadbeefdeadbeefdeadbeefdeadbeef.mdx",
"README.md", // no UUID — should pass through unchanged
]
for (const filename of notionExports) {
const clean = stripNotionUUIDs(filename)
console.log(`${filename}\n → ${clean}\n`)
}
// Expected output:
// Getting Started a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4.md
// → Getting Started.md
//
// API Reference 00112233445566778899aabbccddeeff.md
// → API Reference.md
//
// Changelog deadbeefdeadbeefdeadbeefdeadbeef.mdx
// → Changelog.mdx
//
// README.md
// → README.md
getSortWeight
function getSortWeight(content: string): number
Use getSortWeight to extract a numeric sort position from a documentation file's frontmatter, so you can order pages in a sidebar or navigation structure.
Reach for this when you're building a custom doc pipeline and need to sort a collection of MDX or Markdown files by their declared position before rendering navigation. It's the sorting primitive that powers --by-topic organization in skrypt generate.
It reads the first frontmatter field it finds among sidebar_position, order, weight, and position — in that priority order. If none are present, or if the file has no frontmatter at all, it returns Infinity so the file naturally sorts to the end of any list.
| Name | Type | Required | Description |
|---|---|---|---|
content | string | Yes | Raw file contents including frontmatter — the full string you'd get from fs.readFileSync. Must include the --- delimiters for frontmatter to be detected. |
Returns a number representing the page's sort position. Pass the result directly to an Array.sort comparator to order your pages. Files without a declared position return Infinity, placing them after all explicitly ordered pages.
Heads up:
- Only the first matching field is used — if your frontmatter has both
order: 2andweight: 5,orderwins. Avoid mixing conventions across files in the same project. - The value must be a
numbertype in frontmatter — string values like"3"are ignored and the file sorts to the end.
Example:
// Inline types and mock implementation — no import from autodocs
interface FrontmatterData {
sidebar_position?: unknown;
order?: unknown;
weight?: unknown;
position?: unknown;
}
// Minimal frontmatter parser (mirrors the real implementation's behavior)
function parseFrontmatterRaw(content: string): { data: FrontmatterData | null } {
const match = content.match(/^---\n([\s\S]*?)\n---/);
if (!match) return { data: null };
const data: FrontmatterData = {};
for (const line of match[1].split("\n")) {
const [key, ...rest] = line.split(":");
const value = rest.join(":").trim();
const num = Number(value);
(data as Record<string, unknown>)[key.trim()] = isNaN(num) ? value : num;
}
return { data };
}
function getSortWeight(content: string): number {
const { data } = parseFrontmatterRaw(content);
if (!data) return Infinity;
const weight =
data.sidebar_position ?? data.order ?? data.weight ?? data.position;
return typeof weight === "number" ? weight : Infinity;
}
const files = [
{
name: "advanced-config.mdx",
content: `---\ntitle: Advanced Configuration\norder: 3\n---\n\n# Advanced Configuration`,
},
{
name: "quickstart.mdx",
content: `---\ntitle: Quickstart\nsidebar_position: 1\n---\n\n# Quickstart`,
},
{
name: "concepts.mdx",
content: `---\ntitle: Core Concepts\nsidebar_position: 2\n---\n\n# Core Concepts`,
},
{
name: "changelog.mdx",
content: `---\ntitle: Changelog\n---\n\n# Changelog`,
}, // no position — sorts last
];
const sorted = files.sort(
(a, b) => getSortWeight(a.content) - getSortWeight(b.content)
);
console.log("Sidebar order:");
sorted.forEach((file, index) => {
const weight = getSortWeight(file.content);
console.log(
` ${index + 1}. ${file.name} (weight: ${weight === Infinity ? "∞" : weight})`
);
});
// Expected output:
// Sidebar order:
// 1. quickstart.mdx (weight: 1)
// 2. concepts.mdx (weight: 2)
// 3. advanced-config.mdx (weight: 3)
// 4. changelog.mdx (weight: ∞)