{"version":3,"file":"index.mjs","names":["scanURLs","fs","scanURLs","scanURLs","scanURLs","scanURLs","scanURLs","Astro.scanURLs","Nuxt.scanURLs","ReactRouter.scanURLs","TanStackStart.scanURLs","Waku.scanURLs","Next.scanURLs"],"sources":["../src/print.ts","../src/utils/frontmatter.ts","../src/sample.ts","../src/presets/shared.ts","../src/presets/astro.ts","../src/utils/fs.ts","../src/presets/next.ts","../src/presets/nuxt.ts","../src/presets/react-router.ts","../src/presets/tanstack-start.ts","../src/presets/waku.ts","../src/scan.ts","../src/utils/url.ts","../src/validate/markdown.ts","../src/utils/external-link.ts","../src/validate.ts"],"sourcesContent":["import picocolors from \"picocolors\";\nimport type { ValidateResult } from \"@/validate\";\n\n/**\n * Print validation errors\n */\nexport function printErrors(results: ValidateResult[], throwError = false) {\n  let totalErrors = 0;\n  const logs: string[] = [];\n\n  for (const result of results) {\n    logs.push(\n      picocolors.bold(picocolors.redBright(`Invalid URLs in ${result.file}:`)),\n    );\n\n    for (const error of result.errors) {\n      const message =\n        error.reason instanceof Error ? error.reason.message : error.reason;\n\n      logs.push(\n        `${picocolors.bold(error.url)}: ${message} at ${result.file}:${error.line}:${error.column}`,\n      );\n    }\n\n    logs.push(picocolors.dim(\"------\"));\n\n    totalErrors += result.errors.length;\n  }\n\n  const summary = `${results.length} errored file, ${totalErrors} errors`;\n  logs.push(\n    picocolors.bold(\n      totalErrors > 0\n        ? picocolors.redBright(summary)\n        : picocolors.greenBright(summary),\n    ),\n  );\n\n  if (throwError && totalErrors > 0) {\n    console.error(logs.join(\"\\n\"));\n    process.exit(1);\n  } else {\n    console.log(logs.join(\"\\n\"));\n  }\n}\n","/**\n * Inspired by https://github.com/jonschlinkert/gray-matter\n */\nimport { parse } from \"yaml\";\n\ninterface Output {\n  /**\n   * The matter section, including the delimiter.\n   */\n  matter: string;\n  content: string;\n  data: unknown;\n}\n\nconst regex = /^---\\r?\\n(.+?)\\r?\\n---\\r?\\n?/s;\n\n/**\n * parse frontmatter, it supports only yaml format\n */\nexport function frontmatter(input: string): Output {\n  const output: Output = { matter: \"\", data: {}, content: input };\n  const match = regex.exec(input);\n  if (!match) {\n    return output;\n  }\n\n  // get the raw front-matter block\n  output.matter = match[0];\n  output.content = input.slice(match[0].length);\n\n  const loaded = parse(match[1]);\n  output.data = loaded ?? {};\n\n  return output;\n}\n","import fs from \"node:fs/promises\";\nimport { glob } from \"tinyglobby\";\nimport type { FileObject } from \"./validate\";\nimport { frontmatter } from \"./utils/frontmatter\";\n\nexport type PathToUrl = (path: string) => string | undefined;\n\nexport async function readFileFromPath(\n  file: string,\n  pathToUrl?: PathToUrl,\n): Promise<FileObject> {\n  const content = await fs.readFile(file, \"utf-8\");\n  const parsed = frontmatter(content);\n\n  return {\n    path: file,\n    data: parsed.data as object | undefined,\n    // apply offset to ensure line numbers are correct\n    content:\n      \"\\n\".repeat(countLine(content) - countLine(parsed.content)) +\n      parsed.content,\n    url: pathToUrl ? pathToUrl(file) : undefined,\n  };\n}\n\nexport async function readFiles(\n  patterns: string | readonly string[],\n  options: Partial<{\n    pathToUrl?: PathToUrl;\n  }> = {},\n): Promise<FileObject[]> {\n  const files = await glob(patterns);\n\n  return await Promise.all(\n    files.map((file) => readFileFromPath(file, options.pathToUrl)),\n  );\n}\n\nfunction countLine(s: string) {\n  let out = 0;\n  for (const c of s) {\n    if (c === \"\\n\") out++;\n  }\n\n  return out;\n}\n","import type { PopulateParams, ScanOptions, ScanResult, UrlMeta } from \"@/scan\";\n\nconst defaultPopulate: PopulateParams[string] = [{}];\n\nconst OPTIONAL_CATCH_ALL = /^\\[\\[\\.\\.\\.(.+)\\]\\]$/;\nconst CALCH_ALL = /^\\[\\.\\.\\.(.+)\\]$/;\n\nfunction parseSegments(segments: string[]): {\n  path: string[];\n  params: (\"required\" | \"optional\" | null)[];\n} {\n  const path: string[] = [];\n  const params: (\"required\" | \"optional\" | null)[] = [];\n\n  for (let i = 0; i < segments.length; i++) {\n    const segment = segments[i];\n\n    // route groups\n    if (segment.startsWith(\"(\") && segment.endsWith(\")\")) continue;\n    let match = OPTIONAL_CATCH_ALL.exec(segment);\n\n    if (match) {\n      params.push(\"optional\");\n      path.push(match[1]);\n      continue;\n    }\n\n    match = CALCH_ALL.exec(segment);\n    if (match) {\n      params.push(\"required\");\n      path.push(match[1]);\n      continue;\n    }\n\n    if (segment.startsWith(\"[\") && segment.endsWith(\"]\")) {\n      params.push(\"required\");\n      path.push(segment.slice(1, -1));\n      continue;\n    }\n\n    params.push(null);\n    path.push(segment);\n  }\n\n  return { path, params };\n}\n\ninterface PopulatedRoute {\n  url: string | RegExp;\n  meta?: UrlMeta;\n}\n\nexport function populate(\n  segments: string[],\n  options: ScanOptions,\n): PopulatedRoute[] {\n  const parsed = parseSegments(segments);\n  const countParams = parsed.params.filter((param) => param !== null).length;\n\n  // static\n  if (countParams === 0) {\n    const meta =\n      options.meta?.[segments.length === 0 ? \"/\" : segments.join(\"/\")];\n\n    return [\n      {\n        url: `/${parsed.path.join(\"/\")}`,\n        meta,\n      },\n    ];\n  }\n\n  const out: PopulatedRoute[] = [];\n\n  let params: PopulateParams[string] | undefined;\n  if (options.populate) {\n    params = options.populate[\"/\"];\n    const searchPath = [...segments];\n\n    while (!params && searchPath.length > 0) {\n      params = options.populate[searchPath.join(\"/\")];\n      searchPath.pop();\n    }\n  }\n\n  params ??= defaultPopulate;\n\n  for (const param of params) {\n    // filled url\n    let url = [...parsed.path];\n\n    if (\n      countParams > 1 &&\n      (Array.isArray(param.value) || typeof param.value === \"string\")\n    ) {\n      console.warn(\n        `path ${segments.join(\n          \"/\",\n        )} requires multiple params, an object value for populate is expected.`,\n      );\n    }\n\n    let isFallback = false;\n    for (let i = 0; i < parsed.params.length; i++) {\n      if (parsed.params[i] === null) continue;\n\n      const name = parsed.path[i];\n      let value: string | string[] | undefined;\n\n      if (Array.isArray(param.value) || typeof param.value === \"string\") {\n        value = param.value;\n      } else if (param.value && name in param.value) {\n        value = param.value[name];\n      }\n\n      if (value) {\n        url[i] = typeof value === \"string\" ? value : value.join(\"/\");\n        continue;\n      }\n\n      if (parsed.params[i] === \"optional\") {\n        if (i !== parsed.params.length - 1) {\n          throw new Error(\"Invalid position of optional catch-all\");\n        }\n\n        // without param (optional case)\n        out.push({\n          url: `/${url.slice(0, -1).join(\"/\")}`,\n          meta: param,\n        });\n      }\n\n      url[i] = \"(.+)\";\n      isFallback = true;\n    }\n\n    url = url.filter(Boolean);\n\n    out.push({\n      url: isFallback\n        ? new RegExp(`^\\\\/${url.join(\"\\\\/\")}$`)\n        : `/${url.join(\"/\")}`,\n      meta: param,\n    });\n  }\n\n  return out;\n}\n\n/**\n * Populate Next-like route file paths\n *\n * ```\n * docs/page\n * docs/[slug]/[nested]\n * docs/(group)/[[...optional_catch_all]]\n * docs/(group)/[...catch_all]\n * ```\n */\nexport function populateToScanResult(\n  segments: string[],\n  options: ScanOptions,\n  result: ScanResult,\n) {\n  const out = populate(segments, options);\n\n  for (const entry of out) {\n    if (typeof entry.url === \"string\") {\n      result.urls.set(entry.url, entry.meta ?? {});\n      continue;\n    }\n\n    result.fallbackUrls.push({\n      url: entry.url,\n      meta: entry.meta ?? {},\n    });\n  }\n}\n","import * as path from \"node:path\";\nimport { glob } from \"tinyglobby\";\nimport type { ScanOptions, ScanResult } from \"@/scan\";\nimport { populateToScanResult } from \"./shared\";\n\nexport async function scanURLs(options: ScanOptions = {}): Promise<ScanResult> {\n  const ext = options.extensions ?? [\"astro\", \"md\", \"mdx\"];\n  const cwd = options.cwd ?? process.cwd();\n\n  async function getFiles() {\n    const suffix = ext.length > 0 ? `.{${ext.join(\",\")}}` : \"\";\n\n    const pagesFiles = await glob(`**/*${suffix}`, {\n      cwd: path.join(cwd, \"src/pages\"),\n    });\n\n    return pagesFiles.map((file) => {\n      const parsed = path.parse(file);\n      if (parsed.name === \"index\") return parsed.dir;\n\n      return path.join(parsed.dir, parsed.name);\n    });\n  }\n\n  const result: ScanResult = { urls: new Map(), fallbackUrls: [] };\n  const files = options.pages ?? (await getFiles());\n\n  for (const file of files) {\n    populateToScanResult(file.split(path.sep), options, result);\n  }\n\n  return result;\n}\n","import * as fs from \"node:fs/promises\";\n\nexport function isDirExists(dir: string): Promise<boolean> {\n  return fs\n    .stat(dir)\n    .then((res) => res.isDirectory())\n    .catch(() => false);\n}\n\nexport async function isFileExists(file: string) {\n  try {\n    await fs.access(file);\n    return true;\n  } catch (_error) {\n    return false;\n  }\n}\n","import * as path from \"node:path\";\nimport { glob } from \"tinyglobby\";\nimport type { ScanOptions, ScanResult } from \"@/scan\";\nimport { isDirExists } from \"@/utils/fs\";\nimport { populateToScanResult } from \"./shared\";\n\nexport async function scanURLs(options: ScanOptions = {}): Promise<ScanResult> {\n  const ext = options.extensions ?? [\"js\", \"jsx\", \"tsx\", \"md\", \"mdx\"];\n  const cwd = options.cwd ?? process.cwd();\n\n  async function getFiles() {\n    const suffix = ext.length > 0 ? `.{${ext.join(\",\")}}` : \"\";\n\n    const appFiles = await glob(`**/page${suffix}`, {\n      cwd: (await isDirExists(path.join(cwd, \"src/app\")))\n        ? path.join(cwd, \"src/app\")\n        : path.join(cwd, \"app\"),\n    });\n\n    const pagesFiles = await glob(`**/*${suffix}`, {\n      cwd: (await isDirExists(path.join(cwd, \"src/pages\")))\n        ? path.join(cwd, \"src/pages\")\n        : path.join(cwd, \"pages\"),\n    });\n\n    if (options.pages) appFiles.push(...options.pages);\n\n    return [\n      ...appFiles.map((file) => {\n        const dir = path.dirname(file);\n\n        return dir === \".\" ? \"\" : dir;\n      }),\n      ...pagesFiles.map((file) => {\n        const parsed = path.parse(file);\n        if (parsed.name === \"index\") return parsed.dir;\n\n        return path.join(parsed.dir, parsed.name);\n      }),\n    ];\n  }\n\n  // compatibility\n  if (options.meta)\n    for (const key of Object.keys(options.meta)) {\n      if (!key.endsWith(\"page.tsx\")) continue;\n      let newKey = path.dirname(key);\n      if (newKey === \".\") newKey = \"/\";\n      options.meta[newKey] = options.meta[key];\n      delete options.meta[key];\n    }\n\n  const result: ScanResult = { urls: new Map(), fallbackUrls: [] };\n  const files = await getFiles();\n\n  for (const file of files) {\n    populateToScanResult(\n      file.length === 0 ? [] : file.split(path.sep),\n      options,\n      result,\n    );\n  }\n\n  return result;\n}\n","import * as path from \"node:path\";\nimport { glob } from \"tinyglobby\";\nimport type { ScanOptions, ScanResult } from \"@/scan\";\nimport { isDirExists } from \"@/utils/fs\";\nimport { populateToScanResult } from \"./shared\";\n\nexport async function scanURLs(options: ScanOptions = {}): Promise<ScanResult> {\n  const ext = options.extensions ?? [\"vue\", \"md\", \"mdx\"];\n  const cwd = options.cwd ?? process.cwd();\n\n  async function getFiles() {\n    const suffix = ext.length > 0 ? `.{${ext.join(\",\")}}` : \"\";\n\n    const pagesFiles = await glob(`**/*${suffix}`, {\n      cwd: (await isDirExists(path.join(cwd, \"src/pages\")))\n        ? path.join(cwd, \"src/pages\")\n        : path.join(cwd, \"pages\"),\n    });\n\n    return pagesFiles.map((file) => {\n      const parsed = path.parse(file);\n      if (parsed.name === \"index\") return parsed.dir;\n\n      return path.join(parsed.dir, parsed.name);\n    });\n  }\n\n  const result: ScanResult = { urls: new Map(), fallbackUrls: [] };\n  const files = options.pages ?? (await getFiles());\n\n  for (const file of files) {\n    populateToScanResult(file.split(path.sep), options, result);\n  }\n\n  return result;\n}\n","import type { RouteConfig, RouteConfigEntry } from \"@react-router/dev/routes\";\nimport type { ScanOptions, ScanResult } from \"@/scan\";\nimport { populateToScanResult } from \"./shared\";\n\nexport interface ReactRouterScanOptions extends ScanOptions {\n  preset: \"react-router\";\n  routerConfig: RouteConfig;\n}\n\nexport async function scanURLs(\n  options: ReactRouterScanOptions,\n): Promise<ScanResult> {\n  const { routerConfig } = options;\n\n  async function getFiles() {\n    if (options.pages) return options.pages;\n    const files: string[] = [];\n    const resolved = await routerConfig;\n\n    for (const route of resolved) {\n      resolveEntryFiles(files, route);\n    }\n\n    return files;\n  }\n\n  const result: ScanResult = { urls: new Map(), fallbackUrls: [] };\n\n  for (const file of await getFiles()) {\n    populateToScanResult(file.split(\"/\"), options, result);\n  }\n\n  return result;\n}\n\nfunction resolveEntryFiles(\n  outputFiles: string[],\n  entry: RouteConfigEntry,\n  parent?: string[],\n) {\n  const fullPath = entry.path?.split(\"/\") ?? [];\n  if (parent) fullPath.unshift(...parent);\n\n  if (entry.path) {\n    const combinations: string[][] = [[]];\n    function pushSegment(item: string, newCombination: boolean) {\n      if (newCombination) {\n        const next = combinations.map((combination) => [...combination, item]);\n\n        combinations.push(...next);\n      } else {\n        for (const combination of combinations) {\n          combination.push(item);\n        }\n      }\n    }\n\n    for (let i = 0; i < fullPath.length; i++) {\n      let name = fullPath[i];\n      if (name.length === 0) continue;\n\n      const isOptional = name.endsWith(\"?\");\n      if (isOptional) {\n        name = name.slice(0, -1);\n      }\n\n      // param\n      if (name.startsWith(\":\")) {\n        const paramName = name.slice(1);\n        pushSegment(`[${paramName}]`, isOptional);\n        continue;\n      }\n\n      // splat\n      if (name === \"*\") {\n        pushSegment(`[[...splat]]`, isOptional);\n        continue;\n      }\n\n      pushSegment(name, isOptional);\n    }\n\n    for (const combination of combinations) {\n      outputFiles.push(combination.join(\"/\"));\n    }\n  }\n\n  if (entry.children) {\n    for (const child of entry.children) {\n      resolveEntryFiles(outputFiles, child, fullPath);\n    }\n  }\n}\n","import * as path from \"node:path\";\nimport { glob } from \"tinyglobby\";\nimport type { ScanOptions, ScanResult } from \"@/scan\";\nimport { isDirExists } from \"@/utils/fs\";\nimport { populateToScanResult } from \"./shared\";\n\nexport async function scanURLs(options: ScanOptions = {}): Promise<ScanResult> {\n  const ext = options.extensions ?? [\"tsx\", \"ts\", \"jsx\", \"js\"];\n  const cwd = options.cwd ?? process.cwd();\n\n  async function getFiles() {\n    const suffix = ext.length > 0 ? `.{${ext.join(\",\")}}` : \"\";\n\n    const routesFiles = await glob(`**/*${suffix}`, {\n      cwd: (await isDirExists(path.join(cwd, \"src/routes\")))\n        ? path.join(cwd, \"src/routes\")\n        : path.join(cwd, \"routes\"),\n    });\n\n    const outFiles: string[] = [];\n\n    for (const file of routesFiles) {\n      let segments = file.replaceAll(\"[.]\", \"#\").split(/[/\\\\.]/);\n      // remove file extension\n      segments.pop();\n\n      // escaped dots -> dots\n      segments = segments.map((segment) => segment.replaceAll(\"#\", \".\"));\n      if (segments.at(-1)?.startsWith(\"_\")) continue;\n      if (segments.at(-1) === \"index\") segments.pop();\n\n      const outSegments: string[] = [];\n      for (const name of segments) {\n        if (name.length === 0) continue;\n\n        if (name === \"$\") {\n          outSegments.push(`[[..._splat]]`);\n          continue;\n        }\n\n        if (name.startsWith(\"$\")) {\n          const paramName = name.slice(1);\n          outSegments.push(`[${paramName}]`);\n          continue;\n        }\n\n        outSegments.push(name);\n      }\n\n      outFiles.push(outSegments.join(\"/\"));\n    }\n\n    return outFiles;\n  }\n\n  const result: ScanResult = { urls: new Map(), fallbackUrls: [] };\n  const files = options.pages ?? (await getFiles());\n\n  for (const file of files) {\n    populateToScanResult(file.split(\"/\"), options, result);\n  }\n\n  return result;\n}\n","import * as path from \"node:path\";\nimport { glob } from \"tinyglobby\";\nimport type { ScanOptions, ScanResult } from \"@/scan\";\nimport { isDirExists } from \"@/utils/fs\";\nimport { populateToScanResult } from \"./shared\";\n\nexport async function scanURLs(options: ScanOptions = {}): Promise<ScanResult> {\n  const ext = options.extensions ?? [\"tsx\", \"ts\", \"jsx\", \"js\"];\n  const cwd = options.cwd ?? process.cwd();\n\n  async function getFiles() {\n    const suffix = ext.length > 0 ? `.{${ext.join(\",\")}}` : \"\";\n    const pagesFiles = await glob(`**/*${suffix}`, {\n      cwd: (await isDirExists(path.join(cwd, \"src/pages\")))\n        ? path.join(cwd, \"src/pages\")\n        : path.join(cwd, \"pages\"),\n    });\n\n    const outFiles: string[] = [];\n\n    for (const file of pagesFiles) {\n      const parsed = path.parse(file);\n      if (parsed.name.startsWith(\"_\")) continue;\n\n      const segments = parsed.dir.split(path.sep);\n      if (parsed.name !== \"index\") segments.push(parsed.name);\n\n      const outSegments: string[] = [];\n      for (const name of segments) {\n        // [...param] is optional in Waku\n        if (name.startsWith(\"[...\") && name.endsWith(\"]\")) {\n          outSegments.push(`[${name}]`);\n        } else if (name.length > 0) {\n          outSegments.push(name);\n        }\n      }\n\n      outFiles.push(outSegments.join(\"/\"));\n    }\n\n    return outFiles;\n  }\n\n  const result: ScanResult = { urls: new Map(), fallbackUrls: [] };\n  const files = options.pages ?? (await getFiles());\n\n  for (const file of files) {\n    populateToScanResult(file.split(\"/\"), options, result);\n  }\n\n  return result;\n}\n","import * as Astro from \"./presets/astro\";\nimport * as Next from \"./presets/next\";\nimport * as Nuxt from \"./presets/nuxt\";\nimport * as ReactRouter from \"./presets/react-router\";\nimport * as TanStackStart from \"./presets/tanstack-start\";\nimport * as Waku from \"./presets/waku\";\n\nexport type PopulateParams = Record<\n  string,\n  {\n    value?: string[] | string | Record<string, string[] | string>;\n    hashes?: string[];\n    queries?: Record<string, string>[];\n  }[]\n>;\n\nexport interface ScanOptions {\n  /**\n   * path of pages (e.g. `/docs/page.tsx`)\n   *\n   * App Router format only\n   **/\n  pages?: string[];\n  cwd?: string;\n\n  populate?: PopulateParams;\n  meta?: Record<string, UrlMeta>;\n  extensions?: string[];\n}\n\nexport type ScanPresetOptions =\n  | (ScanOptions & {\n      /**\n       * @default next\n       */\n      preset?: \"next\" | \"astro\" | \"nuxt\" | \"waku\" | \"tanstack-start\";\n    })\n  | ReactRouter.ReactRouterScanOptions;\n\nexport interface ScanResult {\n  urls: Map<string, UrlMeta>;\n\n  fallbackUrls: {\n    url: RegExp;\n    meta: UrlMeta;\n  }[];\n}\n\nexport interface UrlMeta {\n  hashes?: string[];\n  queries?: Record<string, string>[];\n}\n\nexport async function scanURLs(\n  options: ScanPresetOptions = {},\n): Promise<ScanResult> {\n  switch (options.preset) {\n    case \"astro\":\n      return Astro.scanURLs(options);\n    case \"nuxt\":\n      return Nuxt.scanURLs(options);\n    case \"react-router\":\n      return ReactRouter.scanURLs(options);\n    case \"tanstack-start\":\n      return TanStackStart.scanURLs(options);\n    case \"waku\":\n      return Waku.scanURLs(options);\n    default:\n      return Next.scanURLs(options);\n  }\n}\n","/**\n * Split path into segments, trailing/leading slashes are removed\n */\nfunction splitPath(path: string): string[] {\n  return path.split(\"/\").filter((p) => p.length > 0);\n}\n\nexport function resolveUrl(base: string, relative: string) {\n  const v1 = splitPath(base);\n  const v2 = splitPath(relative);\n\n  while (v2.length > 0) {\n    switch (v2[0]) {\n      case \"..\":\n        v1.pop();\n        break;\n      case \".\":\n        break;\n      default:\n        v1.push(v2[0]);\n    }\n\n    v2.shift();\n  }\n\n  return v1.join(\"/\");\n}\n","import type { Root, RootContent } from \"mdast\";\nimport { remark } from \"remark\";\nimport remarkGfm from \"remark-gfm\";\nimport remarkMdx from \"remark-mdx\";\nimport type { PluggableList } from \"unified\";\nimport { visit } from \"unist-util-visit\";\nimport type {\n  Detector,\n  FileObject,\n  ResolutionConfig,\n  ValidateError,\n} from \"@/validate\";\n\nexport interface MarkdownConfig {\n  /**\n   * scan href from attributes in MDX components\n   */\n  components?: Record<\n    string,\n    {\n      attributes: string[];\n    }\n  >;\n\n  remarkPlugins?: PluggableList;\n\n  /**\n   * control how URLs are scanned from Markdown content, override all default behaviours.\n   */\n  onNode?: (node: RootContent) => {\n    hrefs: string[];\n  };\n}\n\nexport function createMarkdownValidator(\n  config: MarkdownConfig,\n  detector: Detector,\n) {\n  const {\n    components = {},\n    remarkPlugins = [],\n    onNode = (node) => {\n      if (node.type === \"link\") {\n        return {\n          hrefs: [node.url],\n        };\n      }\n\n      if (\n        (node.type === \"mdxJsxFlowElement\" ||\n          node.type === \"mdxJsxTextElement\") &&\n        node.name &&\n        node.name in components\n      ) {\n        const analyze = components[node.name];\n\n        const hrefs: string[] = [];\n        for (const attr of node.attributes) {\n          // cannot analyze non-primitive inputs\n          if (attr.type !== \"mdxJsxAttribute\" || typeof attr.value !== \"string\")\n            continue;\n          if (!analyze.attributes.includes(attr.name)) continue;\n\n          hrefs.push(attr.value);\n        }\n\n        return { hrefs };\n      }\n    },\n  } = config;\n\n  const mdProcessor = remark().use(remarkGfm).use(remarkPlugins);\n  const mdxProcessor = remark()\n    .use(remarkMdx)\n    .use(remarkGfm)\n    .use(remarkPlugins);\n\n  return {\n    async validate(\n      file: FileObject,\n      resolution: ResolutionConfig,\n    ): Promise<ValidateError[]> {\n      const errors: ValidateError[] = [];\n      const tasks: Promise<void>[] = [];\n      const processor = file.path.endsWith(\".mdx\") ? mdxProcessor : mdProcessor;\n      const vfile = {\n        path: file.path,\n        value: file.content,\n      };\n\n      let tree = processor.parse(vfile);\n      tree = (await processor.run(tree, vfile)) as Root;\n\n      visit(tree, (node) => {\n        // ignore generated nodes\n        if (!node.position || node.type === \"root\") return;\n        const pos = node.position;\n        const scanned = onNode(node);\n        if (!scanned) return;\n\n        for (const href of scanned.hrefs) {\n          tasks.push(\n            detector\n              .detect(href, resolution)\n              .then((err) => {\n                if (!err || err.type !== \"error\") return;\n\n                errors.push({\n                  url: href,\n                  line: pos.start.line,\n                  column: pos.start.column,\n                  reason: err.reason,\n                });\n              })\n              .catch((err: Error) => {\n                errors.push({\n                  url: href,\n                  line: pos.start.line,\n                  column: pos.start.column,\n                  reason: err,\n                });\n              }),\n          );\n        }\n      });\n\n      await Promise.all(tasks);\n      return errors;\n    },\n  };\n}\n","export interface ExternalLinkConfig {\n  validate?: (url: URL) => Promise<ExternalLinkResult>;\n}\n\nexport type ExternalLinkResult =\n  | {\n      success: true;\n    }\n  | {\n      success: false;\n      message?: string;\n    };\n\nexport function externalLink(config: ExternalLinkConfig) {\n  const { validate } = config;\n\n  return async (url: string): Promise<ExternalLinkResult> => {\n    const parsed = new URL(url);\n    if (validate) return validate(parsed);\n\n    if (parsed.hostname === \"localhost\") return { success: true };\n\n    try {\n      const res = await fetch(parsed, {\n        method: \"HEAD\",\n      });\n\n      if (!res.ok) {\n        if (res.status === 404) return { success: false, message: \"not found\" };\n        // ignore redirect etc.\n        if (res.status >= 300 && res.status < 400) return { success: true };\n\n        return {\n          success: false,\n          message: `${url} responded status ${res.status}`,\n        };\n      }\n\n      return { success: true };\n    } catch (e) {\n      if (e instanceof Error) return { success: false, message: e.message };\n      return { success: false };\n    }\n  };\n}\n","import * as path from \"node:path\";\nimport type { ScanResult } from \"@/scan\";\nimport { type PathToUrl, readFileFromPath } from \"./sample\";\nimport { isFileExists } from \"./utils/fs\";\nimport { resolveUrl } from \"./utils/url\";\nimport {\n  createMarkdownValidator,\n  type MarkdownConfig,\n} from \"./validate/markdown\";\nimport { externalLink, type ExternalLinkConfig } from \"./utils/external-link\";\n\nexport interface ValidateResult {\n  file: string;\n\n  /**]\n   * @deprecated use `errors` instead\n   */\n  detected: DetectedError[];\n  errors: ValidateError[];\n}\n\nexport interface ValidateError {\n  url: string;\n  line: number;\n  column: number;\n  reason: ErrorReason | Error;\n}\n\nexport type ErrorReason = \"not-found\" | \"invalid-fragment\" | \"invalid-query\";\n\nexport interface ResolutionConfig {\n  /**\n   * Base URL to resolve relative URLs\n   */\n  baseUrl?: string;\n\n  /**\n   * Base directory to resolve relative file paths\n   */\n  baseDir?: string;\n\n  /**\n   * Generate URL from file paths, used for relative file path detection.\n   *\n   * Default to searching in input files.\n   */\n  pathToUrl?: PathToUrl;\n}\n\nexport interface DetectorConfig {\n  /**\n   * Available URLs (including hashes and query parameters)\n   */\n  scanned: ScanResult;\n\n  /**\n   * don't validate the fragment/hash of URLs\n   *\n   * @defaultValue false\n   */\n  ignoreFragment?: boolean;\n\n  /**\n   * don't validate the query of URLs\n   *\n   * @defaultValue false\n   */\n  ignoreQuery?: boolean;\n\n  /**\n   * Check external urls\n   *\n   * @defaultValue false\n   */\n  checkExternal?: boolean | ExternalLinkConfig;\n\n  /**\n   * Check relative paths (e.g. `[My File](./my-file.md)`)\n   *\n   * - `exists`: ensure the file exists.\n   * - `as-url`: resolve & check the public URL of referenced file, requires one of these to be defined:\n   *    - `pathToUrl` option.\n   *    - `file.url` in input file objects.\n   * - `false` (default): ignore.\n   */\n  checkRelativePaths?: \"exists\" | \"as-url\" | false;\n\n  /**\n   * Check relative URLs (e.g. `[My File](./my-page)`)\n   *\n   * - `true` (default): resolve & check the relative URL, requires one of these to be defined:\n   *   - `file.url` in input file objects.\n   *   - `pathToUrl` option.\n   *   - `baseUrl` option.\n   * - `false`: ignore.\n   */\n  checkRelativeUrls?: boolean;\n\n  /**\n   * Allowed hrefs, can be:\n   * - a list of hrefs\n   * - a function that returns `true` for allowed href\n   */\n  whitelist?: string[] | ((url: string) => boolean);\n\n  /**\n   * Determinate the type of pathname\n   */\n  determinatePathname?: (pathname: string) => Awaitable<PathnameType>;\n}\n\nexport interface ValidateConfig extends ResolutionConfig, DetectorConfig {\n  markdown?: MarkdownConfig;\n}\n\ntype PathnameType = \"url\" | \"relative-file-path\" | \"relative-url\";\n\ntype Awaitable<T> = T | Promise<T>;\n\nexport interface FileObject {\n  path: string;\n  content: string;\n\n  data?: object;\n\n  /**\n   * URL of page, required for relative url detection\n   */\n  url?: string;\n}\n\nconst mdExtensions = [\".md\", \".mdx\"];\nconst supportedExtensions = mdExtensions;\n\n/**\n * Validate markdown files\n *\n * @param files - file paths or file objects\n * @param config - configurations\n */\nexport async function validateFiles(\n  files: (string | FileObject)[],\n  config: ValidateConfig,\n): Promise<ValidateResult[]> {\n  const detector = createDetector(config);\n  const markdownValidator = createMarkdownValidator(\n    config.markdown ?? {},\n    detector,\n  );\n\n  const normalized = await Promise.all(\n    files.map(async (file) =>\n      typeof file === \"string\"\n        ? await readFileFromPath(file, config.pathToUrl)\n        : file,\n    ),\n  );\n  const defaultPathToUrl: PathToUrl = (path) => {\n    for (const file of normalized) {\n      if (file.path === path && file.url) return file.url;\n    }\n  };\n\n  async function run(file: FileObject): Promise<ValidateResult> {\n    const resolution: ResolutionConfig = {\n      baseUrl: file.url\n        ? file.url.split(\"/\").slice(0, -1).join(\"/\")\n        : config.baseUrl,\n      baseDir: path.dirname(file.path),\n      pathToUrl: config.pathToUrl ?? defaultPathToUrl,\n    };\n    const ext = path.extname(file.path);\n\n    let errors: ValidateError[] = [];\n    if (mdExtensions.includes(ext)) {\n      errors = await markdownValidator.validate(file, resolution);\n    } else {\n      console.warn(\n        `format unsupported: ${ext}, supported: ${supportedExtensions.join(\n          \", \",\n        )}`,\n      );\n    }\n\n    return {\n      file: file.path,\n      errors,\n      get detected() {\n        return errors.map(generateLegacyError);\n      },\n    };\n  }\n\n  return (await Promise.all(normalized.map(run))).filter(\n    (err) => err.errors.length > 0,\n  );\n}\n\nexport interface Detector {\n  detect: (\n    href: string,\n    resolution: ResolutionConfig,\n  ) => Promise<{ type: \"error\"; reason: Error | ErrorReason } | undefined>;\n}\n\nfunction createDetector(config: DetectorConfig): Detector {\n  const PathnameRegex = /^([^?#]*)(\\?[^#]*)?(#.*)?$/;\n  const {\n    checkRelativePaths = false,\n    checkExternal = false,\n    ignoreFragment = false,\n    ignoreQuery = false,\n    checkRelativeUrls = true,\n    whitelist,\n    determinatePathname = (pathname) => {\n      if (!pathname.startsWith(\".\")) return \"url\";\n\n      if (pathname.endsWith(\".md\") || pathname.endsWith(\".mdx\")) {\n        return \"relative-file-path\";\n      }\n\n      return \"relative-url\";\n    },\n  } = config;\n  const externalLinkChecker =\n    checkExternal === false\n      ? null\n      : externalLink(typeof checkExternal === \"object\" ? checkExternal : {});\n\n  let isWhiteListed: ((href: string) => boolean) | undefined;\n  if (typeof whitelist === \"function\") {\n    isWhiteListed = whitelist;\n  } else if (Array.isArray(whitelist)) {\n    const whitelistSet = new Set(whitelist);\n    isWhiteListed = (href) => whitelistSet.has(href);\n  }\n\n  function parsePathname(pathname: string) {\n    const match = PathnameRegex.exec(pathname);\n    if (!match) return { pathname };\n\n    return {\n      pathname: match[1],\n      query: match[2]?.slice(1),\n      fragment: match[3]?.slice(1),\n    };\n  }\n\n  return {\n    async detect(href, { baseDir, baseUrl, pathToUrl }) {\n      if (href.startsWith(\"mailto:\") || isWhiteListed?.(href)) return;\n\n      if (href.match(/https?:\\/\\//)) {\n        if (!externalLinkChecker) return;\n\n        const result = await externalLinkChecker(href);\n        if (result.success) return;\n\n        return {\n          type: \"error\",\n          reason: result.message ? new Error(result.message) : \"not-found\",\n        };\n      }\n\n      let { pathname, query, fragment } = parsePathname(href);\n\n      if (pathname.length === 0 || pathname === \"./\") return;\n\n      switch (await determinatePathname(pathname)) {\n        case \"relative-url\":\n          if (!checkRelativeUrls) return;\n          if (!baseUrl)\n            throw new Error(\n              `relative URL ${pathname} detected, but 'baseUrl' option is missing.`,\n            );\n          pathname = resolveUrl(baseUrl, pathname);\n          break;\n        case \"relative-file-path\": {\n          if (!checkRelativePaths) return;\n\n          const filePath = path.join(baseDir ?? \"\", pathname);\n          if (checkRelativePaths === \"exists\") {\n            return (await isFileExists(filePath))\n              ? undefined\n              : { type: \"error\", reason: \"not-found\" };\n          } else if (checkRelativePaths === \"as-url\") {\n            if (!pathToUrl)\n              throw new Error(\n                `'checkRelativePaths: as-url' is set, but 'pathToUrl' option is missing.`,\n              );\n\n            const url = pathToUrl(filePath);\n            if (!url) return;\n            pathname = url;\n          }\n          break;\n        }\n      }\n\n      if (!pathname.startsWith(\"/\")) pathname = `/${pathname}`;\n      let meta = config.scanned.urls.get(pathname);\n      if (!meta) {\n        meta = config.scanned.fallbackUrls.find((fallbackUrl) => {\n          return fallbackUrl.url.test(pathname);\n        })?.meta;\n      }\n\n      if (!meta)\n        return {\n          type: \"error\",\n          reason: \"not-found\",\n        };\n\n      if (\n        fragment &&\n        !ignoreFragment &&\n        meta.hashes &&\n        !meta.hashes.includes(fragment)\n      ) {\n        return { type: \"error\", reason: \"invalid-fragment\" };\n      }\n\n      if (\n        query &&\n        !ignoreQuery &&\n        meta.queries &&\n        !meta.queries.some(\n          (item) => new URLSearchParams(item).toString() === query,\n        )\n      ) {\n        return { type: \"error\", reason: \"invalid-query\" };\n      }\n    },\n  };\n}\n\nexport type DetectedError = [\n  url: string,\n  line: number,\n  column: number,\n  reason: ErrorReason | Error,\n];\n\nfunction generateLegacyError(v: ValidateError): DetectedError {\n  return [v.url, v.line, v.column, v.reason];\n}\n\nexport type {\n  ExternalLinkConfig,\n  ExternalLinkResult,\n} from \"./utils/external-link\";\n"],"mappings":"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"}