Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 66 additions & 1 deletion CMS-README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,42 @@ From page `user-guide/concepts/agents/state.mdx`:

**Slug generation:** The content collection uses a custom `generateId` function in `src/content.config.ts` that shares the same normalization logic (`normalizePathToSlug`) as link resolution. This ensures consistency between how pages are identified and how links resolve to them.

### 5. API Reference Links (`@api` shorthand)

**What it does:** Provides a shorthand format for linking to API reference pages that's cleaner than relative paths.

**Syntax:**
```markdown
<!-- Python API -->
[@api/python/strands.agent.agent](link text)
[@api/python/strands.agent.agent#AgentResult](link text with anchor)

<!-- TypeScript API -->
[@api/typescript/Agent](link text)
[@api/typescript/Agent#constructor](link text with anchor)
```

**How it works:**

1. Links starting with `@api/` are detected by `isApiShorthand()` in `src/util/links.ts`
2. `resolveApiShorthand()` converts them to absolute paths (e.g., `/api/python/strands.agent.agent/`)
3. `PageLink.astro` applies the site's base path for correct URL generation

**Why use this format:**
- Cleaner than relative paths with `../api-reference/python/...`
- Doesn't break when the linking page moves to a different directory
- Matches the actual URL structure of the generated API docs
- Validated against the content collection at build time

**Examples:**
```markdown
<!-- Instead of this (fragile, verbose): -->
[AgentResult](../api-reference/python/agent/agent_result.md#strands.agent.agent_result.AgentResult)

<!-- Use this (clean, stable): -->
[AgentResult](@api/python/strands.agent.agent_result#AgentResult)
```

## Configuration (`astro.config.mjs`)

The main config ties everything together:
Expand Down Expand Up @@ -537,4 +573,33 @@ Each testimonial is a JSON file:
"icon": "https://example.com/company-logo.png",
"order": 1
}
```
```

## Temporary Migration Files

The following files were created to support the MkDocs → Astro migration and should be deleted once migration is complete:

### Link Conversion Utilities

These files handle converting old MkDocs-style API reference links to the new `@api` shorthand format:

- `src/util/api-link-converter.ts` - Utility functions to detect and convert old API links
- `test/api-link-converter.test.ts` - Tests for the link converter

### Migration Scripts

These scripts transform MkDocs markdown to Astro-compatible format at build time:

- `scripts/update-docs.ts` - Main transformation script (converts admonitions, tabs, API links, etc.)
- `scripts/update-quickstart.ts` - Quickstart-specific transformations
- `test/update-docs.test.ts` - Tests for the update-docs transformations

### When to Delete

Once the migration is complete and all documentation is committed in Astro format:

1. Run `npm run docs:update` one final time to apply all transformations
2. Commit the transformed files directly (no longer keeping MkDocs format in source control)
3. Delete the files listed above
4. Remove the `docs:update` and `docs:revert` scripts from `package.json`
5. Update this README to remove references to the migration process
3 changes: 2 additions & 1 deletion CMS-TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
- [X] Add API documentation generation/integration for Python and TypeScript SDKs
- [ ] Fix type-checking
- [ ] Look into markdown
- [ ] Add header links to Python/TypeScript method sections (api/python/strands.agent.agent/)
- [X] Add header links to Python/TypeScript method sections (api/python/strands.agent.agent/)
- [ ] Migrate all files and remove conversion scripts

## After Launch
- [ ] Move asset files to proper location (currently in `docs/assets/`, should be in `src/content/docs/assets/`)
Expand Down
29 changes: 29 additions & 0 deletions scripts/update-docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { readdir, readFile, writeFile, mkdir, unlink } from "fs/promises";
import { join, dirname } from "path";
import { updateQuickstart } from "./update-quickstart.js";
import { getCommunityLabeledFiles } from "../src/sidebar.js";
import { convertApiLink, isOldApiLink } from "../src/util/api-link-converter.js";

const DOCS_DIR = "docs";
const OUTPUT_DIR = "src/content/docs";
Expand Down Expand Up @@ -451,6 +452,33 @@ function convertBrToSelfClosing(content: string): string {
return content.replace(/<br\s*(?!\/)>/gi, "<br />");
}

/**
* Convert old MkDocs-style API reference links to the new @api shorthand format.
*
* Old formats:
* - Python: `../api-reference/python/agent/agent_result.md#strands.agent.agent_result.AgentResult`
* - TypeScript: `../api-reference/typescript/classes/BedrockModel.html`
*
* New formats:
* - Python: `@api/python/strands.agent.agent_result#AgentResult`
* - TypeScript: `@api/typescript/BedrockModel`
*/
function convertApiLinks(content: string): string {
// Match markdown links with potentially nested brackets in the text
// This handles cases like [`list[ToolSpec]`](url)
const markdownLinkPattern = /\[([^\]]*(?:\[[^\]]*\][^\]]*)*)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;

return content.replace(markdownLinkPattern, (match, text, url) => {
if (isOldApiLink(url)) {
const newUrl = convertApiLink(url);
if (newUrl) {
return `[${text}](${newUrl})`;
}
}
return match;
});
}

/**
* Remove community_contribution_banner macro from content
* The banner is rendered via a component based on `community: true` frontmatter
Expand Down Expand Up @@ -601,6 +629,7 @@ function processFile(content: string, explicitTitle?: string, hasCommunityLabel?
newContent = convertMkdocsTabs(newContent);
newContent = convertHtmlCommentsToJsx(newContent);
newContent = convertBrToSelfClosing(newContent);
newContent = convertApiLinks(newContent);

// Handle H1 heading and title frontmatter
const h1Title = extractH1Title(newContent);
Expand Down
4 changes: 2 additions & 2 deletions scripts/update-quickstart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ function convertQuickstartToCards(content: string): string {
<LinkCard
title="Python Quickstart"
description="$1"
href="$2"
href="../python/"
/>
`
);
Expand All @@ -64,7 +64,7 @@ function convertQuickstartToCards(content: string): string {
`<LinkCard
title="TypeScript Quickstart (Experimental)"
description="$3"
href="$4"
href="../typescript/"
/>
</CardGrid>
`
Expand Down
18 changes: 7 additions & 11 deletions src/components/PageLink.astro
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,13 @@ const { resolvedHref, found } = resolveHref(href, currentPath, docSlugs)

// Log warning in development if link wasn't found
if (!found) {
if (!href.includes("/api-reference/")) {
const route = Astro.locals.starlightRoute
console.warn([
`[PageLink] On page "${route?.entry?.filePath}"`,
`could not resolve "${href}"`,
`currentPath: "${currentPath}"`,
].join(', '))
}
const route = Astro.locals.starlightRoute
console.warn([
`[PageLink] On page "${route?.entry?.filePath}"`,
`could not resolve "${href}"`,
`currentPath: "${currentPath}"`,
].join(', '))
}
---

<a href={resolvedHref} {...rest}>
<slot />
</a>
<a href={resolvedHref} {...rest}><slot /></a>
136 changes: 136 additions & 0 deletions src/util/api-link-converter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/**
* Utility to convert old MkDocs-style API reference links to the new @api shorthand format.
*
* Old formats:
* - Python: `../api-reference/python/agent/agent_result.md#strands.agent.agent_result.AgentResult`
* - TypeScript: `../api-reference/typescript/classes/BedrockModel.html`
*
* New formats:
* - Python: `@api/python/strands.agent.agent_result#AgentResult`
* - TypeScript: `@api/typescript/BedrockModel`
*/

/**
* Pattern to match old Python API links.
* Captures: path segments and optional hash with full dotted path
*/
const PYTHON_API_PATTERN = /^(\.\.\/)*api-reference\/python\/([^#]+)\.md(#(.+))?$/

/**
* Pattern to match old TypeScript API links.
* Captures: classes/interfaces subdirectory and the type name
*/
const TS_API_PATTERN = /^(\.\.\/)*api-reference\/typescript\/(?:classes|interfaces)\/([^.]+)\.html(#(.+))?$/

/**
* Check if a link is an old-style API reference link that needs conversion.
*/
export function isOldApiLink(link: string): boolean {
return PYTHON_API_PATTERN.test(link) || TS_API_PATTERN.test(link)
}

/**
* Convert an old Python API link to the new @api shorthand format.
*
* The hash fragment contains the full dotted path (e.g., `strands.agent.agent_result.AgentResult`).
* We extract the module path (everything up to the last segment) and the symbol (last segment).
*
* Examples:
* - `../api-reference/python/agent/agent_result.md#strands.agent.agent_result.AgentResult`
* -> `@api/python/strands.agent.agent_result#AgentResult`
* - `../api-reference/python/models/model.md#strands.models.model.Model.get_config`
* -> `@api/python/strands.models.model#Model.get_config`
* - `../api-reference/python/models/model.md` (no hash)
* -> `@api/python/strands.models.model`
*/
export function convertPythonApiLink(link: string): string | null {
const match = link.match(PYTHON_API_PATTERN)
if (!match) return null

const pathPart = match[2] ?? '' // e.g., "agent/agent_result" or "models/model"
const hashContent = match[4] // e.g., "strands.agent.agent_result.AgentResult" or undefined

if (hashContent) {
// Hash contains the full dotted path - extract module and symbol
// The module path is typically the part that matches the file structure
// The symbol is what comes after (class name, method, etc.)

// Find where the module path ends and the symbol begins
// Module paths follow the pattern: strands.{path segments matching file structure}
const pathSegments = pathPart.split('/')
const modulePrefix = 'strands.' + pathSegments.join('.')

if (hashContent.startsWith(modulePrefix)) {
// Everything after the module prefix is the symbol
const symbolPart = hashContent.slice(modulePrefix.length)
if (symbolPart.startsWith('.')) {
// There's a symbol after the module path
return `@api/python/${modulePrefix}#${symbolPart.slice(1)}`
} else if (symbolPart === '') {
// Hash points to the module itself
return `@api/python/${modulePrefix}`
}
}

// Fallback: use the hash content directly to determine module
// This handles cases where the hash might not perfectly match the path
const hashParts = hashContent.split('.')
// Find the likely module boundary (usually before a capitalized class name)
let moduleEndIndex = hashParts.length
for (let i = 1; i < hashParts.length; i++) {
const part = hashParts[i]
if (part && /^[A-Z]/.test(part)) {
moduleEndIndex = i
break
}
}

const modulePath = hashParts.slice(0, moduleEndIndex).join('.')
const symbol = hashParts.slice(moduleEndIndex).join('.')

if (symbol) {
return `@api/python/${modulePath}#${symbol}`
} else {
return `@api/python/${modulePath}`
}
} else {
// No hash - convert path to dotted module notation
const modulePath = 'strands.' + pathPart.split('/').join('.')
return `@api/python/${modulePath}`
}
}

/**
* Convert an old TypeScript API link to the new @api shorthand format.
*
* Examples:
* - `../api-reference/typescript/classes/BedrockModel.html` -> `@api/typescript/BedrockModel`
* - `../api-reference/typescript/interfaces/BedrockModelOptions.html` -> `@api/typescript/BedrockModelOptions`
* - `../api-reference/typescript/classes/Agent.html#constructor` -> `@api/typescript/Agent#constructor`
*/
export function convertTypeScriptApiLink(link: string): string | null {
const match = link.match(TS_API_PATTERN)
if (!match) return null

const typeName = match[2] // e.g., "BedrockModel"
const anchor = match[4] // e.g., "constructor" or undefined

if (anchor) {
return `@api/typescript/${typeName}#${anchor}`
}
return `@api/typescript/${typeName}`
}

/**
* Convert any old-style API link to the new @api shorthand format.
* Returns null if the link is not an API reference link.
*/
export function convertApiLink(link: string): string | null {
if (PYTHON_API_PATTERN.test(link)) {
return convertPythonApiLink(link)
}
if (TS_API_PATTERN.test(link)) {
return convertTypeScriptApiLink(link)
}
return null
}
47 changes: 47 additions & 0 deletions src/util/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,40 @@
* using slugs everywhere, though that's always an option.
*/

/**
* Check if a link uses the @api shorthand format.
* Supported formats:
* - @api/python/strands.module.path
* - @api/python/strands.module.path#SubPath
* - @api/typescript/ClassName
* - @api/typescript/ClassName#method
*
* @param link - The href to check
* @returns true if the link uses @api shorthand
*/
export function isApiShorthand(link: string): boolean {
return link.startsWith('@api/')
}

/**
* Resolve an @api shorthand link to an absolute path.
*
* @param link - The @api shorthand link (e.g., `@api/python/strands.agent.agent`)
* @returns The resolved absolute path (e.g., `/api/python/strands.agent.agent/`)
*/
export function resolveApiShorthand(link: string): string {
// Remove the @api/ prefix
const withoutPrefix = link.slice(5) // '@api/'.length === 5

// Split path and anchor
const hashIndex = withoutPrefix.indexOf('#')
const pathPart = hashIndex !== -1 ? withoutPrefix.slice(0, hashIndex) : withoutPrefix
const anchor = hashIndex !== -1 ? withoutPrefix.slice(hashIndex) : ''

// The path is already in the correct format (python/strands.module or typescript/ClassName)
return `/api/${pathPart}/${anchor}`
}

/**
* Get the site's base URL path, stripped of trailing slash for consistent concatenation.
* This is used to build URLs that work correctly when the site is deployed at a subpath.
Expand Down Expand Up @@ -193,6 +227,7 @@ export function findDocSlug(resolvedPath: string, docSlugs: Set<string>): string
* Resolve a potentially relative href to an absolute Astro URL.
*
* This is the main entry point for link resolution. It handles:
* - @api shorthand links (e.g., `@api/python/strands.agent.agent`)
* - Absolute URLs (returned as-is)
* - Anchor-only links (returned as-is)
* - Relative MkDocs-style links (resolved against current path and doc collection)
Expand All @@ -213,6 +248,18 @@ export function resolveHref(
currentPath: string,
docSlugs: Set<string>
): { resolvedHref: string; found: boolean } {
// Handle @api shorthand links
if (isApiShorthand(href)) {
const resolved = resolveApiShorthand(href)
// Extract the slug part (without leading/trailing slashes and anchor)
const hashIndex = resolved.indexOf('#')
const pathOnly = hashIndex !== -1 ? resolved.slice(0, hashIndex) : resolved
const slugPart = pathOnly.replace(/^\//, '').replace(/\/$/, '')
const found = docSlugs.has(slugPart)
// Apply base path for @api links since they resolve to absolute paths
return { resolvedHref: pathWithBase(resolved), found }
}

if (!isRelativeLink(href)) {
return { resolvedHref: href, found: true }
}
Expand Down
Loading
Loading