mirror of
https://github.com/forcedotcom/afv-library.git
synced 2026-08-03 05:41:08 +08:00
55 lines
1.5 KiB
TypeScript
55 lines
1.5 KiB
TypeScript
import fs from "fs"
|
|
import path from "path"
|
|
|
|
export interface BrokenLink {
|
|
link: string
|
|
text: string
|
|
reason: string
|
|
}
|
|
|
|
/**
|
|
* Extracts relative markdown links from content.
|
|
* Ignores URLs (http/https), anchors-only (#heading), and mailto links.
|
|
*/
|
|
export function extractRelativeLinks(content: string): Array<{ text: string; link: string }> {
|
|
const linkRegex = /\[([^\]]*)\]\(([^)]+)\)/g
|
|
const links: Array<{ text: string; link: string }> = []
|
|
let match: RegExpExecArray | null
|
|
|
|
while ((match = linkRegex.exec(content)) !== null) {
|
|
const link = match[2]
|
|
if (link.startsWith("http://") || link.startsWith("https://") || link.startsWith("#") || link.startsWith("mailto:")) {
|
|
continue
|
|
}
|
|
const filePath = link.split("#")[0]
|
|
if (filePath) {
|
|
links.push({ text: match[1], link: filePath })
|
|
}
|
|
}
|
|
|
|
return links
|
|
}
|
|
|
|
/**
|
|
* Checks all relative markdown links in a SKILL.md resolve to existing files.
|
|
*/
|
|
export function findBrokenLinks(skillDir: string, content: string): BrokenLink[] {
|
|
const links = extractRelativeLinks(content)
|
|
const broken: BrokenLink[] = []
|
|
|
|
for (const { text, link } of links) {
|
|
const resolved = path.resolve(skillDir, link)
|
|
|
|
if (!resolved.startsWith(path.resolve(skillDir))) {
|
|
broken.push({ link, text, reason: `Escapes skill directory via ../` })
|
|
continue
|
|
}
|
|
|
|
if (!fs.existsSync(resolved)) {
|
|
broken.push({ link, text, reason: `File not found: ${link}` })
|
|
}
|
|
}
|
|
|
|
return broken
|
|
}
|