Checks
Functions
checkFrontmatter
function checkFrontmatter(filePath: string, content: string, relPath: string): QAIssue[]
Use checkFrontmatter to validate the frontmatter block of a generated MDX doc file and surface any structural issues before publishing.
Call this as part of a QA pass after skrypt generate produces your documentation files — it catches missing, malformed, or incomplete frontmatter that would break your doc site's navigation, search indexing, or metadata rendering.
It parses the --- delimited frontmatter block at the top of the file content and checks for required fields and formatting correctness. Each problem found becomes a discrete QAIssue entry you can log, display, or fail a CI step on.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
filePath | string | Yes | Absolute path to the MDX file on disk — used to read file context and produce actionable error locations |
content | string | Yes | Full raw text content of the MDX file, including the frontmatter block — pass the result of fs.readFileSync directly |
relPath | string | Yes | Path relative to your docs root (e.g. api/users/create.mdx) — appears in issue messages so you know exactly which file to fix |
Returns
An array of QAIssue objects, each describing one frontmatter problem found in the file. Returns an empty array if the frontmatter is valid. Pass the result to a reporter or accumulate issues across multiple files to produce a full QA report — a non-empty array is a signal to block publishing or fail CI.
Heads up
- If the file has no frontmatter block at all (no opening
---), that itself is reported as an issue — a missing block won't silently pass. contentmust be the raw file string, not a parsed object. Pre-parsing the frontmatter before passing it in will cause the function to report a missing block.
Example:
// Inline types — do not import from autodocs
type QAIssue = {
file: string
line?: number
message: string
severity: 'error' | 'warning'
}
// Minimal self-contained implementation matching the real function's behavior
function checkFrontmatter(filePath: string, content: string, relPath: string): QAIssue[] {
const issues: QAIssue[] = []
const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/)
if (!fmMatch) {
issues.push({
file: relPath,
line: 1,
message: 'Missing frontmatter block. Add a --- delimited block at the top of the file.',
severity: 'error',
})
return issues
}
const fmBody = fmMatch[1]
if (!fmBody.match(/^title:/m)) {
issues.push({
file: relPath,
line: 1,
message: 'Frontmatter is missing required field: title',
severity: 'error',
})
}
if (!fmBody.match(/^description:/m)) {
issues.push({
file: relPath,
line: 1,
message: 'Frontmatter is missing recommended field: description',
severity: 'warning',
})
}
return issues
}
// Simulate a generated MDX file missing its description field
const filePath = '/workspace/my-docs/content/docs/api/payments/create.mdx'
const relPath = 'api/payments/create.mdx'
const contentMissingDescription = `---
title: Create Payment
---
Use \`createPayment\` to charge a customer...
`
const contentNoFrontmatter = `# Create Payment
Use \`createPayment\` to charge a customer...
`
const issues1 = checkFrontmatter(filePath, contentMissingDescription, relPath)
const issues2 = checkFrontmatter(filePath, contentNoFrontmatter, relPath)
console.log('Issues (missing description):', issues1)
// Issues (missing description): [ { file: 'api/payments/create.mdx', line: 1, message: 'Frontmatter is missing recommended field: description', severity: 'warning' } ]
console.log('Issues (no frontmatter):', issues2)
// Issues (no frontmatter): [ { file: 'api/payments/create.mdx', line: 1, message: 'Missing frontmatter block...', severity: 'error' } ]
const allClear = checkFrontmatter(filePath, `---\ntitle: Create Payment\ndescription: Charge a customer.\n---\n\nContent here.`, relPath)
console.log('All clear:', allClear) // All clear: []
checkHeadings
function checkHeadings(content: string, relPath: string): QAIssue[]
Use checkHeadings to catch structural heading problems in generated MDX documentation before publishing — things like multiple H1s, skipped heading levels, or missing top-level headings that would break navigation and accessibility.
Reach for this as part of a post-generation QA pass. After skrypt generate writes MDX files to your output directory, run checkHeadings on each file's content to surface issues before they reach your doc site.
It parses the document line by line, tracking heading depth and count while ignoring headings inside code blocks, so it won't false-positive on markdown examples in your docs.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
content | string | Yes | The full MDX/Markdown file content to analyze — pass the raw string from fs.readFileSync. |
relPath | string | Yes | The file path relative to your docs root (e.g. api/users.mdx) — used to identify which file each issue belongs to in the returned results. |
Returns
An array of QAIssue objects, each describing a single heading violation with the file path, line number, and a human-readable message. Returns an empty array if no issues are found. Feed the results into your CI reporter or log them to the console to fail a build when issues exist.
Heads up
- Heading level jumps are flagged even if they're intentional — for example, going from
##directly to####will produce an issue. Review results before treating every finding as a hard error. - Content inside fenced code blocks (
```) is skipped, but indented code blocks are not — keep your examples fenced.
Example:
import { readFileSync, readdirSync } from "fs";
import { join } from "path";
// Inline types — do not import from autodocs
interface QAIssue {
file: string;
line: number;
message: string;
}
// Inline mock of checkHeadings — replace with the real import in your project
function checkHeadings(content: string, relPath: string): QAIssue[] {
const issues: QAIssue[] = [];
const lines = content.split("\n");
let h1Count = 0;
let lastLevel = 0;
let inCodeBlock = false;
lines.forEach((line, index) => {
if (line.startsWith("```")) {
inCodeBlock = !inCodeBlock;
return;
}
if (inCodeBlock) return;
const match = line.match(/^(#{1,6})\s/);
if (!match) return;
const level = match[1].length;
const lineNumber = index + 1;
if (level === 1) {
h1Count++;
if (h1Count > 1) {
issues.push({ file: relPath, line: lineNumber, message: "Multiple H1 headings found — only one H1 is allowed per document." });
}
}
if (lastLevel > 0 && level > lastLevel + 1) {
issues.push({ file: relPath, line: lineNumber, message: `Heading level jumped from H${lastLevel} to H${level} — skipped levels break document structure.` });
}
lastLevel = level;
});
if (h1Count === 0) {
issues.push({ file: relPath, line: 1, message: "No H1 heading found — every document should have exactly one top-level heading." });
}
return issues;
}
// Simulate a QA pass over generated MDX output
const docContent = `
# Authentication
## API Keys
Use API keys to authenticate requests.
#### Rotating Keys
Jump straight to H4 — this will be flagged.
# Another Top-Level Heading
This second H1 will also be flagged.
\`\`\`markdown
## This heading inside a code block is ignored
\`\`\`
`.trim();
try {
const issues = checkHeadings(docContent, "api/authentication.mdx");
if (issues.length === 0) {
console.log("✅ No heading issues found.");
} else {
console.log(`❌ Found ${issues.length} heading issue(s):\n`);
issues.forEach(({ file, line, message }) => {
console.log(` [${file}:${line}] ${message}`);
});
}
} catch (err) {
console.error("QA check failed:", err);
}
// Expected output:
// ❌ Found 3 heading issue(s):
//
// [api/authentication.mdx:7] Heading level jumped from H2 to H4 — skipped levels break document structure.
// [api/authentication.mdx:10] Multiple H1 headings found — only one H1 is allowed per document.
checkCodeBlocks
function checkCodeBlocks(content: string, relPath: string): QAIssue[]
Use checkCodeBlocks to catch malformed or empty code blocks in your generated MDX documentation before publishing.
Call this as part of a QA pass after skrypt generate produces output files — it's the right tool when you want to validate that every fenced code block in a doc file opens and closes correctly and contains actual content.
It scans the file line by line, tracking opening and closing triple-backtick fences. Any block that's unclosed at end-of-file, or that contains no lines between the fences, is flagged as an issue.
| Name | Type | Required | Description |
|---|---|---|---|
content | string | Yes | The full text of the MDX file to validate — pass the result of fs.readFileSync(path, 'utf8') |
relPath | string | Yes | The file path relative to your docs root, used to populate the file field on returned issues so you know which file to fix |
Returns an array of QAIssue objects, one per problem found. Each issue identifies the file and line number of the offending code block. An empty array means the file is clean. Collect results across all your output files and surface them together to get a full picture of what needs fixing before you ship.
Heads up:
- An unclosed code block (missing closing
```) will be reported at the line where the block opened, not at end-of-file — so the line number points you directly to where to add the closing fence. - Nested or indented fences (e.g., inside a blockquote) are not treated specially — any line starting with
```toggles the open/close state.
Example:
import { readFileSync, readdirSync } from 'fs'
import { join } from 'path'
// ── Inline types (do not import from autodocs) ──
interface QAIssue {
file: string
line: number
message: string
}
// ── Inline implementation ──
function checkCodeBlocks(content: string, relPath: string): QAIssue[] {
const issues: QAIssue[] = []
const lines = content.split('\n')
let inCodeBlock = false
let codeBlockStart = 0
let codeBlockLines = 0
for (let i = 0; i < lines.length; i++) {
const line = lines[i]
if (line.startsWith('```')) {
if (!inCodeBlock) {
inCodeBlock = true
codeBlockStart = i + 1
codeBlockLines = 0
} else {
if (codeBlockLines === 0) {
issues.push({
file: relPath,
line: codeBlockStart,
message: 'Empty code block',
})
}
inCodeBlock = false
}
} else if (inCodeBlock) {
if (line.trim().length > 0) codeBlockLines++
}
}
if (inCodeBlock) {
issues.push({
file: relPath,
line: codeBlockStart,
message: 'Unclosed code block',
})
}
return issues
}
// ── Example: validate generated docs before publishing ──
const docFiles: Record<string, string> = {
'docs/authentication.mdx': `
# Authentication
Use the API key from your dashboard.
\`\`\`ts
const client = new MyClient({ apiKey: 'sk_live_4xZ9...' })
\`\`\`
Here's an empty block that will be flagged:
\`\`\`ts
\`\`\`
And here's an unclosed block that will also be flagged:
\`\`\`ts
const broken = true
`.trim(),
'docs/webhooks.mdx': `
# Webhooks
Register an endpoint to receive events.
\`\`\`ts
app.post('/webhook', (req, res) => {
console.log(req.body)
res.sendStatus(200)
})
\`\`\`
`.trim(),
}
const allIssues: QAIssue[] = []
for (const [relPath, content] of Object.entries(docFiles)) {
const issues = checkCodeBlocks(content, relPath)
allIssues.push(...issues)
}
if (allIssues.length === 0) {
console.log('✅ All code blocks are valid.')
} else {
console.log(`❌ Found ${allIssues.length} issue(s):\n`)
for (const issue of allIssues) {
console.log(` ${issue.file}:${issue.line} — ${issue.message}`)
}
}
// Expected output:
// ❌ Found 2 issue(s):
//
// docs/authentication.mdx:10 — Empty code block
// docs/authentication.mdx:14 — Unclosed code block
checkComponents
function checkComponents(content: string, relPath: string): QAIssue[]
Use checkComponents to validate MDX documentation files and surface structural issues before they reach your doc site.
Call this as part of a QA pipeline after skrypt generate produces MDX output — it catches problems like malformed component usage, broken syntax, or structural anti-patterns that would silently break rendering.
It scans the file line by line, skipping fenced code blocks so it only flags issues in live MDX markup (not example code). Each issue it finds includes the line number and a description of what's wrong.
| Name | Type | Required | Description |
|---|---|---|---|
content | string | Yes | The full text content of an MDX file — pass the result of fs.readFileSync(path, 'utf8') |
relPath | string | Yes | The file path relative to your docs root (e.g. api/users.mdx) — used to identify the source file in returned issues |
Returns an array of QAIssue objects, each with the file path, line number, and a description of the problem. An empty array means the file passed all checks. Pipe failing issues to your CI logs or aggregate them across files to build a full QA report before deploying.
Heads up:
- Pass
relPathas a relative path, not an absolute one — it's used as a display label in issue output, so/Users/me/project/docs/api/users.mdxwill make your QA report harder to read. - Content inside fenced code blocks (
```) is intentionally skipped — issues inside example code won't be flagged.
Example:
import { readFileSync, readdirSync } from "fs";
import { join } from "path";
// Inline types — do not import from autodocs
interface QAIssue {
file: string;
line: number;
message: string;
}
// Minimal mock of checkComponents for a self-contained example
function checkComponents(content: string, relPath: string): QAIssue[] {
const issues: QAIssue[] = [];
const lines = content.split("\n");
let inCodeBlock = false;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.trimStart().startsWith("```")) {
inCodeBlock = !inCodeBlock;
continue;
}
if (inCodeBlock) continue;
// Flag unclosed JSX-style component tags
if (/<[A-Z][a-zA-Z]+[^>]*[^/]>/.test(line) && !line.includes(`</`)) {
const tagMatch = line.match(/<([A-Z][a-zA-Z]+)/);
if (tagMatch && !line.includes("/>")) {
issues.push({
file: relPath,
line: i + 1,
message: `Possibly unclosed component: <${tagMatch[1]}>`,
});
}
}
}
return issues;
}
// Simulate checking a generated MDX file
const exampleMdx = `
# Payment API
Use the \`createPayment\` function to charge a customer.
<Callout type="warning">
Always validate the amount server-side before calling this endpoint.
\`\`\`ts
// This unclosed tag inside a code block should NOT be flagged:
// <BadComponent
\`\`\`
<ParamTable fields={paymentFields} />
`;
try {
const issues = checkComponents(exampleMdx, "api/payments.mdx");
if (issues.length === 0) {
console.log("✅ No issues found.");
} else {
console.log(`❌ Found ${issues.length} issue(s):\n`);
for (const issue of issues) {
console.log(` ${issue.file}:${issue.line} — ${issue.message}`);
}
}
} catch (err) {
console.error("QA check failed:", err);
}
// Expected output:
// ❌ Found 1 issue(s):
//
// api/payments.mdx:6 — Possibly unclosed component: <Callout>
checkLinks
function checkLinks(content: string, relPath: string, _allFiles: Set<string>): QAIssue[]
Use checkLinks to validate internal links in your documentation content and surface broken or malformed references before they reach users.
Reach for this during a QA pass over generated or authored MDX/Markdown files — it's especially useful in CI pipelines where you want to catch dead links before publishing. It fits naturally after content generation, as a final validation step before writing output to disk.
The function scans each line of the content for internal link syntax, skipping code blocks to avoid false positives, and checks whether the referenced paths resolve to real files in your documentation tree.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
content | string | Yes | The raw Markdown or MDX file content to scan — pass the full file string, not a parsed AST. |
relPath | string | Yes | The file's path relative to your docs root (e.g. "api/users.mdx") — used to resolve relative links correctly. |
_allFiles | Set<string> | Yes | A set of all known file paths in your docs output, relative to the docs root — links are validated against this set. |
Returns
Returns an array of QAIssue objects, each describing a broken or suspicious link with its line number and a human-readable message. An empty array means all internal links resolved successfully. Pass the returned issues to your reporter or accumulate them across files to produce a full QA summary.
- Heads up: Only internal links are checked — external URLs (starting with
http://orhttps://) are ignored entirely. If you need to validate outbound links, you'll need a separate network-based checker. - Links inside fenced code blocks are intentionally skipped, so example code containing fake URLs won't generate false positives.
Example:
type QAIssue = {
file: string
line: number
type: string
message: string
severity: "error" | "warning"
}
function checkLinks(content: string, relPath: string, allFiles: Set<string>): QAIssue[] {
const issues: QAIssue[] = []
const lines = content.split("\n")
let inCodeBlock = false
const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g
for (let i = 0; i < lines.length; i++) {
const line = lines[i]
if (line.trimStart().startsWith("```")) {
inCodeBlock = !inCodeBlock
continue
}
if (inCodeBlock) continue
let match: RegExpExecArray | null
while ((match = linkRegex.exec(line)) !== null) {
const href = match[2]
// Skip external links and anchors-only
if (href.startsWith("http://") || href.startsWith("https://") || href.startsWith("#")) {
continue
}
// Strip anchor fragment for file resolution
const filePart = href.split("#")[0]
if (!filePart) continue
// Resolve relative to the current file's directory
const dir = relPath.includes("/") ? relPath.slice(0, relPath.lastIndexOf("/") + 1) : ""
const resolved = (dir + filePart).replace(/\/\.\//g, "/")
if (!allFiles.has(resolved)) {
issues.push({
file: relPath,
line: i + 1,
type: "broken-link",
message: `Link target not found: "${href}" (resolved to "${resolved}")`,
severity: "error",
})
}
}
}
return issues
}
// Simulate a docs output tree
const allDocFiles = new Set([
"api/users.mdx",
"api/payments.mdx",
"guides/quickstart.mdx",
"index.mdx",
])
const fileContent = `
# Users API
See the [quickstart guide](../guides/quickstart.mdx) to get up and running.
Check out [payments](../api/payments.mdx) for billing endpoints.
This link is broken: [missing page](../api/webhooks.mdx).
\`\`\`ts
// Code blocks are skipped: [fake link](../not/real.mdx)
\`\`\`
`.trim()
const issues = checkLinks(fileContent, "api/users.mdx", allDocFiles)
if (issues.length === 0) {
console.log("All links valid.")
} else {
console.log(`Found ${issues.length} issue(s):`)
for (const issue of issues) {
console.log(` [${issue.severity.toUpperCase()}] Line ${issue.line}: ${issue.message}`)
}
}
// Expected output:
// Found 1 issue(s):
// [ERROR] Line 7: Link target not found: "../api/webhooks.mdx" (resolved to "api/webhooks.mdx")
checkSecurity
function checkSecurity(content: string, relPath: string): QAIssue[]
Use checkSecurity to scan generated documentation content for security issues before publishing — catching things like exposed API keys, hardcoded secrets, or sensitive file paths that may have leaked into your docs.
When you're running a QA pass over generated MDX files, call this function on each file's content to surface security problems alongside other quality checks. It fits naturally into a pre-publish pipeline or CI step where you want to block docs that accidentally expose credentials.
The function scans line-by-line, skipping fenced code blocks where secrets might appear intentionally as examples, and returns a structured list of issues with line numbers so you can pinpoint exactly where the problem is.
| Name | Type | Required | Description |
|---|---|---|---|
content | string | Yes | The full text content of the documentation file to scan — typically the result of reading an MDX or Markdown file from disk. |
relPath | string | Yes | The file path relative to your docs root (e.g. api/authentication.mdx) — used to identify which file each issue belongs to in the returned results. |
Returns an array of QAIssue objects, each describing a detected problem with a file, line, severity, and message. An empty array means the file is clean. Pass the results to your QA reporter or use .length > 0 to fail a CI check.
Heads up:
- Code blocks (
```fences) are intentionally skipped — secrets shown as documentation examples won't trigger false positives. - This checks the generated output, not your source code. Run it after
skrypt generatecompletes, not before.
Example:
type Severity = 'error' | 'warning' | 'info'
interface QAIssue {
file: string
line: number
severity: Severity
message: string
}
function checkSecurity(content: string, relPath: string): QAIssue[] {
const issues: QAIssue[] = []
const lines = content.split('\n')
let inCodeBlock = false
const patterns: Array<{ regex: RegExp; message: string; severity: Severity }> = [
{
regex: /sk_live_[a-zA-Z0-9]{24,}/,
message: 'Possible Stripe live secret key exposed',
severity: 'error',
},
{
regex: /AIza[0-9A-Za-z\-_]{35}/,
message: 'Possible Google API key exposed',
severity: 'error',
},
{
regex: /password\s*[:=]\s*["'][^"']{4,}["']/i,
message: 'Hardcoded password detected',
severity: 'error',
},
{
regex: /Bearer\s+[a-zA-Z0-9\-_]{20,}/,
message: 'Hardcoded Bearer token detected',
severity: 'warning',
},
]
for (let i = 0; i < lines.length; i++) {
const line = lines[i]
if (line.trimStart().startsWith('```')) {
inCodeBlock = !inCodeBlock
continue
}
if (inCodeBlock) continue
for (const { regex, message, severity } of patterns) {
if (regex.test(line)) {
issues.push({ file: relPath, line: i + 1, severity, message })
}
}
}
return issues
}
// Simulate a generated MDX file that accidentally contains a leaked key
const docContent = `
# Authentication
To authenticate requests, pass your API key in the Authorization header.
## Example
\`\`\`bash
curl -H "Authorization: Bearer sk_live_4xKj9mNpQrTvWxYz2aBcDeFgHiJkLmNo" https://api.example.com/v1/users
\`\`\`
In production, never hardcode credentials. Use environment variables instead.
The internal deploy script uses password: "hunter2_deploy_prod" to push releases.
`
try {
const issues = checkSecurity(docContent, 'api/authentication.mdx')
if (issues.length === 0) {
console.log('✅ No security issues found')
} else {
console.log(`🚨 Found ${issues.length} security issue(s):\n`)
for (const issue of issues) {
console.log(` [${issue.severity.toUpperCase()}] ${issue.file}:${issue.line} — ${issue.message}`)
}
}
} catch (err) {
console.error('Security check failed:', err)
}
// Expected output:
// 🚨 Found 1 security issue(s):
//
// [ERROR] api/authentication.mdx:14 — Hardcoded password detected
//
// (The Bearer token inside the ``` code block is correctly skipped)
checkContentQuality
function checkContentQuality(content: string, relPath: string): QAIssue[]
Use checkContentQuality to catch thin, malformed, or incomplete documentation before it ships — returning a list of actionable issues for any generated MDX file.
Reach for this after skrypt generate produces output files and you want to validate them programmatically, for example in a CI step that fails the build when docs fall below a quality threshold. It's the same check that powers skrypt's built-in QA pass, exposed so you can run it on individual files or integrate it into your own tooling.
The function strips frontmatter and fenced code blocks before analysis, so word counts and content checks reflect prose only — a file that's 90% code examples won't be penalized for low word count.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
content | string | Yes | Raw file contents of the MDX doc, including frontmatter — read directly from disk with fs.readFileSync |
relPath | string | Yes | Path to the file relative to your docs root (e.g. api/users.mdx) — used to identify which file each issue belongs to in the returned results |
Returns
An array of QAIssue objects, each describing a specific problem found in the file. Each issue includes the file path, a severity level, and a human-readable message. An empty array means the file passed all checks. Pipe failing issues to your logger or use them to set a non-zero exit code in CI.
Heads up
- Frontmatter and code blocks are excluded from word-count checks, so a file with a long code example but minimal prose description can still trigger a low-content issue.
- Pass the relative path consistently (don't mix absolute and relative paths across calls) — the path is embedded verbatim in each returned issue and is what you'll use to trace problems back to files.
Example:
import { readFileSync, readdirSync } from "fs";
import { join } from "path";
type QAIssueSeverity = "error" | "warning" | "info";
interface QAIssue {
file: string;
severity: QAIssueSeverity;
message: string;
}
// Inline implementation matching skrypt's checkContentQuality behavior
function checkContentQuality(content: string, relPath: string): QAIssue[] {
const issues: QAIssue[] = [];
// Strip frontmatter and code blocks before analysis
const stripped = content
.replace(/^---[\s\S]*?---/, "")
.replace(/```[\s\S]*?```/g, "");
const wordCount = stripped.trim().split(/\s+/).filter(Boolean).length;
if (wordCount < 50) {
issues.push({
file: relPath,
severity: "warning",
message: `Low word count (${wordCount} words) — consider expanding the description`,
});
}
if (!content.startsWith("---")) {
issues.push({
file: relPath,
severity: "error",
message: "Missing frontmatter — file must begin with a --- block",
});
}
if (!/^#{1,3} /m.test(stripped)) {
issues.push({
file: relPath,
severity: "warning",
message: "No headings found — add section headings to improve scannability",
});
}
return issues;
}
// Simulate reading generated docs from an output directory
const generatedDocs: Record<string, string> = {
"api/users.mdx": `---
title: Users API
---
## Create a user
Use \`createUser\` to register a new account and receive a user object back.
Pass the returned \`user.id\` to subsequent calls to act on behalf of this user.
\`\`\`ts
const user = await createUser({ email: "ada@example.com" });
\`\`\`
This endpoint validates the email format and rejects duplicates with a 409 error.
`,
"api/webhooks.mdx": `No frontmatter here. Short file.`,
};
try {
const allIssues: QAIssue[] = [];
for (const [relPath, content] of Object.entries(generatedDocs)) {
const issues = checkContentQuality(content, relPath);
allIssues.push(...issues);
}
if (allIssues.length === 0) {
console.log("✅ All docs passed quality checks");
} else {
console.log(`Found ${allIssues.length} issue(s):\n`);
for (const issue of allIssues) {
const icon = issue.severity === "error" ? "❌" : "⚠️";
console.log(`${icon} [${issue.severity.toUpperCase()}] ${issue.file}`);
console.log(` ${issue.message}\n`);
}
const hasErrors = allIssues.some((i) => i.severity === "error");
if (hasErrors) process.exit(1);
}
} catch (err) {
console.error("QA check failed:", err);
process.exit(1);
}
// Expected output:
// Found 3 issue(s):
//
// ⚠️ [WARNING] api/webhooks.mdx
// Low word count (5 words) — consider expanding the description
//
// ❌ [ERROR] api/webhooks.mdx
// Missing frontmatter — file must begin with a --- block
//
// ⚠️ [WARNING] api/webhooks.mdx
// No headings found — add section headings to improve scannability
checkScreenshots
function checkScreenshots(content: string, relPath: string, outputDir: string): QAIssue[]
Use checkScreenshots to catch broken or missing screenshot references in your MDX documentation before publishing.
Call this as part of a QA pass over generated docs — it's especially useful after running skrypt generate, when image paths may reference screenshots that haven't been captured yet or were saved to the wrong directory.
The function scans each line of a doc file's content for image references (e.g., ), skips fenced code blocks so it doesn't flag example code, then checks whether the referenced files actually exist relative to the output directory. Each broken reference becomes a QAIssue you can surface in CI or a pre-publish report.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
content | string | Yes | The full text of the MDX/Markdown file to scan — pass the raw file contents, not a parsed AST |
relPath | string | Yes | The file's path relative to your docs root (e.g., api/auth.mdx) — used to populate issue locations so you know which file the problem came from |
outputDir | string | Yes | Absolute or relative path to the directory where generated docs live — screenshot paths are resolved against this root when checking for existence |
Returns
Returns an array of QAIssue objects, one per broken screenshot reference. Each issue includes the file path, line number, and a description of the problem. An empty array means all referenced screenshots exist. Feed the results into your QA reporter or fail CI if issues.length > 0.
Heads up
- The function resolves screenshot paths relative to
outputDir, not relative to the file itself — make sureoutputDiris the docs root, not the subdirectory the file lives in. - Lines inside fenced code blocks (
```) are intentionally skipped, so screenshot paths shown as examples in your docs won't trigger false positives.
Example:
import { readFileSync, existsSync, statSync } from 'fs'
import { join } from 'path'
interface QAIssue {
file: string
line: number
message: string
severity: 'error' | 'warning'
}
function checkScreenshots(content: string, relPath: string, outputDir: string): QAIssue[] {
const issues: QAIssue[] = []
const lines = content.split('\n')
let inCodeBlock = false
for (let i = 0; i < lines.length; i++) {
const line = lines[i]
if (line.trimStart().startsWith('```')) {
inCodeBlock = !inCodeBlock
continue
}
if (inCodeBlock) continue
// Match markdown image syntax: 
const imagePattern = /!\[.*?\]\(([^)]+)\)/g
let match
while ((match = imagePattern.exec(line)) !== null) {
const imgPath = match[1]
// Only check local paths, not external URLs
if (imgPath.startsWith('http://') || imgPath.startsWith('https://')) continue
const absolutePath = join(outputDir, imgPath)
if (!existsSync(absolutePath)) {
issues.push({
file: relPath,
line: i + 1,
message: `Screenshot not found: ${imgPath} (resolved to ${absolutePath})`,
severity: 'error',
})
}
}
}
return issues
}
// Simulate a doc file that references two screenshots — one exists, one doesn't
const docContent = `
# Authentication
Use the login endpoint to authenticate users.
\`\`\`bash
# This path below should NOT be checked (it's in a code block)

\`\`\`
Here's what the response looks like:


`
const outputDir = './content/docs'
const relPath = 'api/authentication.mdx'
// Simulate one screenshot existing
import { mkdirSync, writeFileSync } from 'fs'
mkdirSync(join(outputDir, 'screenshots'), { recursive: true })
writeFileSync(join(outputDir, 'screenshots/login-response.png'), 'fake-image-data')
const issues = checkScreenshots(docContent, relPath, outputDir)
if (issues.length === 0) {
console.log('✅ All screenshots verified.')
} else {
console.log(`❌ Found ${issues.length} issue(s):\n`)
for (const issue of issues) {
console.log(` [${issue.severity.toUpperCase()}] ${issue.file}:${issue.line} — ${issue.message}`)
}
}
// Expected output:
// ❌ Found 1 issue(s):
//
// [ERROR] api/authentication.mdx:13 — Screenshot not found: ./screenshots/token-flow.png (resolved to content/docs/screenshots/token-flow.png)
checkMdxSyntax
function checkMdxSyntax(content: string, relPath: string): QAIssue[]
Use checkMdxSyntax to catch MDX syntax errors in generated documentation files before they break your doc site build.
Reach for this when you're building a QA pipeline over skrypt's MDX output — for example, validating generated files after skrypt generate runs, or gating a CI step that deploys your docs. It fits naturally after file generation and before any compilation or publishing step.
The function scans the file line by line, tracking context like open code blocks, and flags structural MDX problems (unclosed tags, malformed JSX expressions, invalid frontmatter) that would cause a compiler to fail. It skips content inside fenced code blocks to avoid false positives.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
content | string | Yes | The raw MDX file contents to validate — pass the result of fs.readFileSync(path, 'utf-8') |
relPath | string | Yes | The file path relative to your docs root, used to identify the source of each issue in the returned results (e.g. "api/users.mdx") |
Returns
An array of QAIssue objects, each describing a syntax problem with a line number, message, and severity. An empty array means the file is clean. Pass the results to your reporter or aggregator — if any issues have severity: "error", treat the file as unpublishable and surface them to the developer before the build proceeds.
Heads up
- The check is intentionally lightweight — it catches structural MDX issues, not TypeScript type errors inside
<CodeBlock>components or broken import paths. relPathis used only for labeling issues, not for reading the file. You must read the file contents yourself and pass them ascontent.
Example:
import { readFileSync } from "fs";
// Inline types — do not import from autodocs
interface QAIssue {
file: string;
line?: number;
message: string;
severity: "error" | "warning" | "info";
}
// Minimal mock of checkMdxSyntax for a self-contained example
function checkMdxSyntax(content: string, relPath: string): QAIssue[] {
const issues: QAIssue[] = [];
const lines = content.split("\n");
let inCodeBlock = false;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.trimStart().startsWith("```")) {
inCodeBlock = !inCodeBlock;
continue;
}
if (inCodeBlock) continue;
// Flag unclosed JSX-style tags (simplified heuristic)
const openTags = (line.match(/<[A-Z][a-zA-Z]*(?:\s[^>]*)?\s*>/g) || []).length;
const closeTags = (line.match(/<\/[A-Z][a-zA-Z]*>/g) || []).length;
if (openTags > closeTags) {
issues.push({
file: relPath,
line: i + 1,
message: `Possibly unclosed JSX tag on line ${i + 1}`,
severity: "warning",
});
}
// Flag lone curly braces outside code blocks
if (line.includes("{") && !line.includes("}")) {
issues.push({
file: relPath,
line: i + 1,
message: `Unclosed expression brace on line ${i + 1}`,
severity: "error",
});
}
}
return issues;
}
// Simulate MDX content that came out of `skrypt generate`
const mdxContent = `---
title: Users API
---
# Create User
Use <CreateUserButton to trigger user creation.
Call this endpoint with a valid \`apiKey\`:
\`\`\`ts
const user = await createUser({ name: "Ada Lovelace" });
\`\`\`
The response includes a \`userId\` you can pass to <UserProfile id={userId} />.
Here is a broken expression: { unclosed
`;
const relPath = "api/users.mdx";
const issues = checkMdxSyntax(mdxContent, relPath);
if (issues.length === 0) {
console.log(`✅ ${relPath}: No MDX syntax issues found.`);
} else {
console.log(`❌ ${relPath}: ${issues.length} issue(s) found:\n`);
for (const issue of issues) {
const location = issue.line ? `line ${issue.line}` : "unknown line";
console.log(` [${issue.severity.toUpperCase()}] ${location}: ${issue.message}`);
}
}
// Expected output:
// ❌ api/users.mdx: 2 issue(s) found:
//
// [WARNING] line 6: Possibly unclosed JSX tag on line 6
// [ERROR] line 16: Unclosed expression brace on line 16
checkCodeBlockBraces
function checkCodeBlockBraces(content: string, relPath: string): QAIssue[]
Use checkCodeBlockBraces to catch unescaped curly braces inside fenced code blocks before they cause MDX compilation errors.
Reach for this during a QA or pre-publish pass on generated .md documentation files. When skrypt outputs .md files that are later processed by an MDX pipeline, any { or } inside code blocks gets interpreted as a JSX expression — breaking the build with cryptic acorn parse errors. This function surfaces those problems early, with file and line context, so you can fix or escape them before they reach your doc site.
It only inspects .md files. .mdx files are skipped entirely on the assumption that curly braces there are intentional JSX.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
content | string | Yes | The full text of the file to inspect — pass the result of fs.readFileSync(path, 'utf8') |
relPath | string | Yes | The file's path relative to your docs root (e.g. "api/users.md") — used both to skip .mdx files and to populate the file field on any returned issues |
Returns
An array of QAIssue objects, each describing one offending line — including the file path, line number, and a message identifying the unescaped brace. Returns an empty array when no issues are found or when the file is .mdx. Feed the results into your broader QA report or log them to fail a CI step.
Heads up
- Passing an absolute path as
relPathwon't break anything, but the path will appear verbatim in issue messages — use a relative path so reports are readable across machines. - This is a defensive warning layer, not a fixer. It detects the problem; escaping the braces (e.g. replacing
{with{) is left to you.
Example:
import { readFileSync } from 'fs'
// Inline types — do not import from autodocs
interface QAIssue {
file: string
line: number
message: string
severity: 'error' | 'warning'
}
// Inline implementation for self-contained example
function checkCodeBlockBraces(content: string, relPath: string): QAIssue[] {
if (relPath.endsWith('.mdx')) return []
const issues: QAIssue[] = []
const lines = content.split('\n')
let insideCodeBlock = false
for (let i = 0; i < lines.length; i++) {
const line = lines[i]
if (line.trimStart().startsWith('```')) {
insideCodeBlock = !insideCodeBlock
continue
}
if (insideCodeBlock && (line.includes('{') || line.includes('}'))) {
issues.push({
file: relPath,
line: i + 1,
message: `Unescaped curly brace in code block: ${line.trim()}`,
severity: 'warning',
})
}
}
return issues
}
// Simulate a generated .md file with curly braces in a code block
const markdownContent = `
# createUser
Creates a new user in the system.
\`\`\`typescript
const user = await createUser({ name: "Alice", role: "admin" })
console.log(user.id)
\`\`\`
Pass the returned \`id\` to subsequent calls.
`
async function main() {
try {
const issues = checkCodeBlockBraces(markdownContent, 'api/create-user.md')
if (issues.length === 0) {
console.log('No brace issues found.')
} else {
console.log(`Found ${issues.length} issue(s):\n`)
for (const issue of issues) {
console.log(`[${issue.severity.toUpperCase()}] ${issue.file}:${issue.line} — ${issue.message}`)
}
}
// Expected output:
// Found 1 issue(s):
//
// [WARNING] api/create-user.md:7 — Unescaped curly brace in code block: const user = await createUser({ name: "Alice", role: "admin" })
} catch (err) {
console.error('QA check failed:', err)
}
}
main()