Skip to content

fix(docs): strip .md extension from internal link targets#69

Merged
WomB0ComB0 merged 1 commit into
mainfrom
fix/strip-md-from-link-targets
May 10, 2026
Merged

fix(docs): strip .md extension from internal link targets#69
WomB0ComB0 merged 1 commit into
mainfrom
fix/strip-md-from-link-targets

Conversation

@WomB0ComB0
Copy link
Copy Markdown
Member

Symptom

Clicking any module link from a package landing in the TypeScript SDK (e.g. `index` on `/sdks/typescript/api/logger`) 404s because Mintlify treats the link target literally — `.../index/index.md` is not a routed URL even though the file exists on disk and its extensionless form `.../index/index` returns 200.

Fix

New post-process step (TS + Rust templates) that rewrites every internal markdown link target:

Before After
`label` `label`
`label` `label`
`label` `label`

The `/index.md` → parent-dir collapse aligns with the splice's strip-`/index` registration so URLs match Mintlify's native dir-page resolution.

External URLs (`http://`, `mailto:`, `data:`) and fragment-only links pass through unchanged.

Live state

`sdks/typescript/api/` and `sdks/rust/api/` rewritten in-place so the fix applies immediately when pulled:

  • 207 TS files updated
  • 0 Rust files (cargo-doc-md uses in-page anchors; the step is defensive for embedded README content)

Verified

`curl -sI http://localhost:3340/sdks/typescript/api/logger/index\` → `200 OK`
`curl -sI http://localhost:3340/sdks/typescript/api/logger/logger\` → `307` (Mintlify auto-resolves to first child of `logger/logger/`)

Mintlify routes URLs without the \`.md\` extension. A markdown
link like \`[index](./index/index.md)\` 404s when clicked, even
when the target file exists on disk and its extensionless URL
form (\`/.../index/index\`) returns 200.

Add a post-process step (TS + Rust templates) that rewrites all
internal markdown link targets:

  [label](dir/index.md)  →  [label](dir)
  [label](leaf.md)        →  [label](leaf)
  [label](path.md#frag)   →  [label](path#frag)

Skip URL-scheme targets (http://, mailto:, data:) and qualified
paths starting with #. The /index.md → parent-dir collapse aligns
with the splice's strip-/index registration so URLs match
Mintlify's native dir-page resolution.

Symptom (TS): clicking any module link from a package landing
(e.g. \`[index](./index/index.md)\` on
\`/sdks/typescript/api/logger\`) navigated to \`.../index/index.md\`
which Mintlify treats as a literal path and 404s. After the strip,
it navigates to \`.../index\` (200) which auto-resolves to the
landing.

Live sdks/typescript/api/ and sdks/rust/api/ rewritten in-place
so the fix applies immediately on pull. 207 TS files updated, 0
Rust files (cargo-doc-md uses in-page anchors so no rewrite
needed; the step is defensive for embedded README content).
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 10, 2026

Important

Review skipped

Too many files!

This PR contains 209 files, which is 59 over the limit of 150.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b2db23f6-8d9c-48ea-b0cc-729e43981b42

📥 Commits

Reviewing files that changed from the base of the PR and between 4aac52a and bd5a930.

📒 Files selected for processing (209)
  • automation/source-repo-templates/api-docs.rust.yml
  • automation/source-repo-templates/api-docs.typescript.yml
  • sdks/typescript/api/analytics/index.md
  • sdks/typescript/api/analytics/index/classes/Analytics.md
  • sdks/typescript/api/analytics/index/functions/initAnalytics.md
  • sdks/typescript/api/analytics/index/index.md
  • sdks/typescript/api/analytics/index/interfaces/AnalyticsConfig.md
  • sdks/typescript/api/analytics/index/variables/analytics.md
  • sdks/typescript/api/analytics/index/variables/track.md
  • sdks/typescript/api/analytics/next/functions/ga4Stream.md
  • sdks/typescript/api/analytics/next/functions/withAnalyticsRewrites.md
  • sdks/typescript/api/analytics/next/index.md
  • sdks/typescript/api/analytics/react/functions/AnalyticsProvider.md
  • sdks/typescript/api/analytics/react/functions/useAnalytics.md
  • sdks/typescript/api/analytics/react/index.md
  • sdks/typescript/api/analytics/react/interfaces/AnalyticsProviderProps.md
  • sdks/typescript/api/analytics/react/interfaces/UseAnalyticsReturn.md
  • sdks/typescript/api/analytics/resq/functions/sanitizeGa4Id.md
  • sdks/typescript/api/analytics/resq/index.md
  • sdks/typescript/api/decorators/after/after.fn/functions/afterFn.md
  • sdks/typescript/api/decorators/after/after.fn/index.md
  • sdks/typescript/api/decorators/after/after.types/index.md
  • sdks/typescript/api/decorators/after/after.types/interfaces/AfterConfig.md
  • sdks/typescript/api/decorators/after/after.types/type-aliases/AfterFunc.md
  • sdks/typescript/api/decorators/after/after/functions/after.md
  • sdks/typescript/api/decorators/after/after/index.md
  • sdks/typescript/api/decorators/after/index.md
  • sdks/typescript/api/decorators/before/before.fn/functions/beforeFn.md
  • sdks/typescript/api/decorators/before/before.fn/index.md
  • sdks/typescript/api/decorators/before/before.types/index.md
  • sdks/typescript/api/decorators/before/before/functions/before.md
  • sdks/typescript/api/decorators/before/before/index.md
  • sdks/typescript/api/decorators/before/index.md
  • sdks/typescript/api/decorators/bind/bind.fn/functions/bindFn.md
  • sdks/typescript/api/decorators/bind/bind.fn/index.md
  • sdks/typescript/api/decorators/bind/bind.types/index.md
  • sdks/typescript/api/decorators/bind/bind/functions/bind.md
  • sdks/typescript/api/decorators/bind/bind/index.md
  • sdks/typescript/api/decorators/bind/index.md
  • sdks/typescript/api/decorators/debounce/debounce.fn/functions/debounceFn.md
  • sdks/typescript/api/decorators/debounce/debounce.fn/index.md
  • sdks/typescript/api/decorators/debounce/debounce/functions/debounce.md
  • sdks/typescript/api/decorators/debounce/debounce/index.md
  • sdks/typescript/api/decorators/debounce/index.md
  • sdks/typescript/api/decorators/delay/delay.fn/functions/delayFn.md
  • sdks/typescript/api/decorators/delay/delay.fn/index.md
  • sdks/typescript/api/decorators/delay/delay/functions/delay.md
  • sdks/typescript/api/decorators/delay/delay/index.md
  • sdks/typescript/api/decorators/delay/index.md
  • sdks/typescript/api/decorators/delegate/delegate.fn/functions/delegateFn.md
  • sdks/typescript/api/decorators/delegate/delegate.fn/index.md
  • sdks/typescript/api/decorators/delegate/delegate.types/index.md
  • sdks/typescript/api/decorators/delegate/delegate.types/type-aliases/Delegatable.md
  • sdks/typescript/api/decorators/delegate/delegate/functions/delegate.md
  • sdks/typescript/api/decorators/delegate/delegate/index.md
  • sdks/typescript/api/decorators/delegate/index.md
  • sdks/typescript/api/decorators/exec-time/exec-time.fn/functions/execTimeFn.md
  • sdks/typescript/api/decorators/exec-time/exec-time.fn/index.md
  • sdks/typescript/api/decorators/exec-time/exec-time.types/index.md
  • sdks/typescript/api/decorators/exec-time/exec-time.types/type-aliases/ReportFunction.md
  • sdks/typescript/api/decorators/exec-time/exec-time/functions/execTime.md
  • sdks/typescript/api/decorators/exec-time/exec-time/index.md
  • sdks/typescript/api/decorators/exec-time/index.md
  • sdks/typescript/api/decorators/execute/execute/index.md
  • sdks/typescript/api/decorators/execute/index.md
  • sdks/typescript/api/decorators/index.md
  • sdks/typescript/api/decorators/index/index.md
  • sdks/typescript/api/decorators/memoize-async/index.md
  • sdks/typescript/api/decorators/memoize-async/memoize-async.fn/functions/memoizeAsyncFn.md
  • sdks/typescript/api/decorators/memoize-async/memoize-async.fn/index.md
  • sdks/typescript/api/decorators/memoize-async/memoize-async.types/index.md
  • sdks/typescript/api/decorators/memoize-async/memoize-async.types/interfaces/AsyncMemoizeConfig.md
  • sdks/typescript/api/decorators/memoize-async/memoize-async.types/type-aliases/AsyncMemoizable.md
  • sdks/typescript/api/decorators/memoize-async/memoize-async/functions/memoizeAsync.md
  • sdks/typescript/api/decorators/memoize-async/memoize-async/index.md
  • sdks/typescript/api/decorators/memoize/index.md
  • sdks/typescript/api/decorators/memoize/memoize.fn/functions/memoizeFn.md
  • sdks/typescript/api/decorators/memoize/memoize.fn/index.md
  • sdks/typescript/api/decorators/memoize/memoize.types/index.md
  • sdks/typescript/api/decorators/memoize/memoize.types/interfaces/MemoizeConfig.md
  • sdks/typescript/api/decorators/memoize/memoize.types/type-aliases/Memoizable.md
  • sdks/typescript/api/decorators/memoize/memoize/functions/memoize.md
  • sdks/typescript/api/decorators/memoize/memoize/index.md
  • sdks/typescript/api/decorators/observer/index.md
  • sdks/typescript/api/decorators/observer/observer.types/index.md
  • sdks/typescript/api/decorators/observer/observer/functions/observe.md
  • sdks/typescript/api/decorators/observer/observer/index.md
  • sdks/typescript/api/decorators/rate-limit/index.md
  • sdks/typescript/api/decorators/rate-limit/rate-limit.fn/functions/rateLimitFn.md
  • sdks/typescript/api/decorators/rate-limit/rate-limit.fn/index.md
  • sdks/typescript/api/decorators/rate-limit/rate-limit.types/index.md
  • sdks/typescript/api/decorators/rate-limit/rate-limit.types/interfaces/RateLimitConfigs.md
  • sdks/typescript/api/decorators/rate-limit/rate-limit.types/type-aliases/RateLimitable.md
  • sdks/typescript/api/decorators/rate-limit/rate-limit/functions/rateLimit.md
  • sdks/typescript/api/decorators/rate-limit/rate-limit/index.md
  • sdks/typescript/api/decorators/rate-limit/simple-rate-limit-counter/classes/SimpleRateLimitCounter.md
  • sdks/typescript/api/decorators/rate-limit/simple-rate-limit-counter/index.md
  • sdks/typescript/api/decorators/readonly/index.md
  • sdks/typescript/api/decorators/readonly/readonly.types/index.md
  • sdks/typescript/api/decorators/readonly/readonly/functions/readonly.md
  • sdks/typescript/api/decorators/readonly/readonly/index.md
  • sdks/typescript/api/decorators/throttle-async/index.md
  • sdks/typescript/api/decorators/throttle-async/throttle-async-executor/classes/ThrottleAsyncExecutor.md
  • sdks/typescript/api/decorators/throttle-async/throttle-async-executor/index.md
  • sdks/typescript/api/decorators/throttle-async/throttle-async.fn/functions/throttleAsyncFn.md
  • sdks/typescript/api/decorators/throttle-async/throttle-async.fn/index.md
  • sdks/typescript/api/decorators/throttle-async/throttle-async/functions/throttleAsync.md
  • sdks/typescript/api/decorators/throttle-async/throttle-async/index.md
  • sdks/typescript/api/decorators/throttle/index.md
  • sdks/typescript/api/decorators/throttle/throttle.fn/functions/throttleFn.md
  • sdks/typescript/api/decorators/throttle/throttle.fn/index.md
  • sdks/typescript/api/decorators/throttle/throttle/functions/throttle.md
  • sdks/typescript/api/decorators/throttle/throttle/index.md
  • sdks/typescript/api/decorators/types/index.md
  • sdks/typescript/api/decorators/types/type-aliases/AsyncDecorator.md
  • sdks/typescript/api/decorators/types/type-aliases/Decorator.md
  • sdks/typescript/api/decorators/utils/index.md
  • sdks/typescript/api/dsa/bloom/index.md
  • sdks/typescript/api/dsa/count-min/index.md
  • sdks/typescript/api/dsa/distance/classes/Distance.md
  • sdks/typescript/api/dsa/distance/index.md
  • sdks/typescript/api/dsa/distance/interfaces/Coordinates2D.md
  • sdks/typescript/api/dsa/distance/interfaces/Coordinates3D.md
  • sdks/typescript/api/dsa/distance/interfaces/DistanceResult.md
  • sdks/typescript/api/dsa/dsa/rabin-karp/classes/RabinKarp.md
  • sdks/typescript/api/dsa/dsa/rabin-karp/index.md
  • sdks/typescript/api/dsa/graph/classes/Graph.md
  • sdks/typescript/api/dsa/graph/functions/addValidatedEdge.md
  • sdks/typescript/api/dsa/graph/index.md
  • sdks/typescript/api/dsa/graph/interfaces/Vertex.md
  • sdks/typescript/api/dsa/heap/classes/BoundedHeap.md
  • sdks/typescript/api/dsa/heap/index.md
  • sdks/typescript/api/dsa/index.md
  • sdks/typescript/api/dsa/index/index.md
  • sdks/typescript/api/dsa/lru-cache/classes/LRUCache.md
  • sdks/typescript/api/dsa/lru-cache/index.md
  • sdks/typescript/api/dsa/priority-queue/classes/PriorityQueue.md
  • sdks/typescript/api/dsa/priority-queue/functions/createDeadlineQueue.md
  • sdks/typescript/api/dsa/priority-queue/functions/createMaxHeap.md
  • sdks/typescript/api/dsa/priority-queue/functions/createMinHeap.md
  • sdks/typescript/api/dsa/priority-queue/functions/createPriorityLevelQueue.md
  • sdks/typescript/api/dsa/priority-queue/index.md
  • sdks/typescript/api/dsa/priority-queue/interfaces/PriorityQueueOptions.md
  • sdks/typescript/api/dsa/queue/index.md
  • sdks/typescript/api/dsa/schemas/index.md
  • sdks/typescript/api/dsa/schemas/type-aliases/GraphEdge.md
  • sdks/typescript/api/dsa/schemas/type-aliases/GraphOptions.md
  • sdks/typescript/api/dsa/schemas/type-aliases/PriorityItemInput.md
  • sdks/typescript/api/dsa/schemas/type-aliases/PriorityQueueOptions.md
  • sdks/typescript/api/dsa/schemas/type-aliases/RabinKarpMultiSearch.md
  • sdks/typescript/api/dsa/schemas/type-aliases/RabinKarpOptions.md
  • sdks/typescript/api/dsa/schemas/type-aliases/RabinKarpSearch.md
  • sdks/typescript/api/dsa/schemas/type-aliases/TrieInsert.md
  • sdks/typescript/api/dsa/schemas/type-aliases/TrieOptions.md
  • sdks/typescript/api/dsa/schemas/type-aliases/TrieSearch.md
  • sdks/typescript/api/dsa/schemas/type-aliases/VertexId.md
  • sdks/typescript/api/dsa/trie/classes/Trie.md
  • sdks/typescript/api/dsa/trie/index.md
  • sdks/typescript/api/http/fetcher/functions/del.md
  • sdks/typescript/api/http/fetcher/functions/fetcher.md
  • sdks/typescript/api/http/fetcher/functions/get.md
  • sdks/typescript/api/http/fetcher/functions/head.md
  • sdks/typescript/api/http/fetcher/functions/options.md
  • sdks/typescript/api/http/fetcher/functions/patch.md
  • sdks/typescript/api/http/fetcher/functions/post.md
  • sdks/typescript/api/http/fetcher/functions/put.md
  • sdks/typescript/api/http/fetcher/index.md
  • sdks/typescript/api/http/index.md
  • sdks/typescript/api/http/index/index.md
  • sdks/typescript/api/http/security/index.md
  • sdks/typescript/api/logger/index.md
  • sdks/typescript/api/logger/index/index.md
  • sdks/typescript/api/logger/logger.decorators/functions/Log.md
  • sdks/typescript/api/logger/logger.decorators/functions/LogClass.md
  • sdks/typescript/api/logger/logger.decorators/functions/LogError.md
  • sdks/typescript/api/logger/logger.decorators/functions/LogTiming.md
  • sdks/typescript/api/logger/logger.decorators/index.md
  • sdks/typescript/api/logger/logger.types/index.md
  • sdks/typescript/api/logger/logger.types/interfaces/LogEntry.md
  • sdks/typescript/api/logger/logger.types/interfaces/LogMethodOptions.md
  • sdks/typescript/api/logger/logger.types/interfaces/LogTimingOptions.md
  • sdks/typescript/api/logger/logger.types/interfaces/LogTransport.md
  • sdks/typescript/api/logger/logger.types/interfaces/LoggerOptions.md
  • sdks/typescript/api/logger/logger/classes/Logger.md
  • sdks/typescript/api/logger/logger/index.md
  • sdks/typescript/api/logger/logger/interfaces/LoggerOptions.md
  • sdks/typescript/api/logger/logger/variables/logger.md
  • sdks/typescript/api/rate-limiting/index.md
  • sdks/typescript/api/rate-limiting/index/index.md
  • sdks/typescript/api/rate-limiting/rate-limit/classes/MemoryRateLimitStore.md
  • sdks/typescript/api/rate-limiting/rate-limit/classes/RedisRateLimitStore.md
  • sdks/typescript/api/rate-limiting/rate-limit/index.md
  • sdks/typescript/api/rate-limiting/throttle/index.md
  • sdks/typescript/api/security/crypto/index.md
  • sdks/typescript/api/security/index.md
  • sdks/typescript/api/security/index/index.md
  • sdks/typescript/api/security/sanitize/index.md
  • sdks/typescript/api/security/validators/functions/containsCommandInjection.md
  • sdks/typescript/api/security/validators/functions/containsHomoglyphs.md
  • sdks/typescript/api/security/validators/functions/containsNoSQLInjection.md
  • sdks/typescript/api/security/validators/functions/containsPathTraversal.md
  • sdks/typescript/api/security/validators/functions/containsSQLInjection.md
  • sdks/typescript/api/security/validators/functions/containsXSSPatterns.md
  • sdks/typescript/api/security/validators/functions/detectThreatPatterns.md
  • sdks/typescript/api/security/validators/functions/getThreatErrorMessage.md
  • sdks/typescript/api/security/validators/functions/isSafeInput.md
  • sdks/typescript/api/security/validators/index.md
  • sdks/typescript/api/security/validators/interfaces/ThreatDetectionResult.md
  • sdks/typescript/api/security/validators/interfaces/ThreatFinding.md

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/strip-md-from-link-targets

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added the area:content MDX/MD documentation content label May 10, 2026
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a post-processing step in the documentation generation workflows for both Rust and TypeScript to strip .md extensions from internal Markdown links, aligning with Mintlify's routing requirements. Feedback identifies several technical limitations in the Python script's regex logic, including its failure to ignore links within code spans, incorrect handling of links with titles, and inconsistent transformations for index.md files. Additionally, the duplication of this complex logic across multiple workflow files was flagged as a maintenance risk, with a recommendation to extract the script into a shared, reusable component.

Comment on lines +425 to +443
link_re = re.compile(r'(?<!\!)\[([^\]]*)\]\(([^)]+)\)')

def strip_md(target: str) -> str:
first_seg = target.split("/", 1)[0]
if ":" in first_seg:
return target
path, _, frag = target.partition("#")
frag = ("#" + frag) if frag else ""
if path.endswith("/index.md"):
path = path[: -len("/index.md")] or "."
elif path.endswith(".md"):
path = path[:-3]
return path + frag

for f in pathlib.Path(".").rglob("*.md"):
text = f.read_text(encoding="utf-8")
def fix(m: re.Match) -> str:
return f"[{m.group(1)}]({strip_md(m.group(2))})"
new = link_re.sub(fix, text)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The regex-based link stripping logic has several limitations that should be addressed:

  • Code Span Safety: The current implementation does not exclude links within backticks or code blocks (e.g., `[label](file.md)`). This violates the general rule requiring parsing logic to respect CommonMark inline code span rules to avoid modifying content inside code snippets.
  • Links with Titles: Markdown links can include titles (e.g., [label](url.md "title")). The regex ([^)]+) captures the title as part of the target group, causing strip_md to fail to remove the extension because the string ends with a quote instead of .md.
  • Index Inconsistency: path.endswith("/index.md") does not match a link that is exactly index.md. In that case, it falls through to the .md strip logic and becomes index, whereas ./index.md becomes ./. This inconsistency may affect how Mintlify resolves directory-level pages.

Consider using a more robust parsing approach that identifies code spans first and separates the URL from any optional title.

References
  1. When processing Markdown/MDX files to escape curly braces for JSX compatibility, ensure the logic ignores content within inline code spans (backticks) to prevent breaking code snippets.

Comment on lines +302 to +328
python3 - <<'PY'
import pathlib
import re

link_re = re.compile(r'(?<!\!)\[([^\]]*)\]\(([^)]+)\)')

def strip_md(target: str) -> str:
first_seg = target.split("/", 1)[0]
if ":" in first_seg:
return target
path, _, frag = target.partition("#")
frag = ("#" + frag) if frag else ""
if path.endswith("/index.md"):
path = path[: -len("/index.md")] or "."
elif path.endswith(".md"):
path = path[:-3]
return path + frag

for f in pathlib.Path(".").rglob("*.md"):
text = f.read_text(encoding="utf-8")
def fix(m: re.Match) -> str:
label, target = m.group(1), m.group(2)
return f"[{label}]({strip_md(target)})"
new = link_re.sub(fix, text)
if new != text:
f.write_text(new, encoding="utf-8")
PY
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This Python script is duplicated from the Rust documentation template. Duplicating complex post-processing logic across multiple workflow files increases maintenance overhead and the risk of inconsistent behavior. Consider extracting this logic into a shared script or a reusable local action to ensure that fixes (such as handling code spans or link titles) are applied consistently across all SDK documentation.

@WomB0ComB0 WomB0ComB0 merged commit 5b07bb1 into main May 10, 2026
14 checks passed
@WomB0ComB0 WomB0ComB0 deleted the fix/strip-md-from-link-targets branch May 10, 2026 11:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:content MDX/MD documentation content

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants