@qualcomm-ui/mdx-vite 3.2.2 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -3617,7 +3617,8 @@ var configSchema = implement().with({
3617
3617
  routingStrategy: z2.union([z2.literal("vite-generouted"), z2.any()]).optional(),
3618
3618
  throwOnError: z2.boolean().optional(),
3619
3619
  typeDocProps: z2.string().optional(),
3620
- typeDocPropsOptions: typeDocPropsSchema.optional()
3620
+ typeDocPropsOptions: typeDocPropsSchema.optional(),
3621
+ validatePageLinks: z2.boolean().optional()
3621
3622
  });
3622
3623
 
3623
3624
  // src/docs-plugin/config/config-loader.ts
@@ -3648,7 +3649,8 @@ var ConfigLoader = class {
3648
3649
  ...conf,
3649
3650
  appDirectory: conf.appDirectory || "app",
3650
3651
  filePath: config3.filepath,
3651
- pageDirectory: conf.pageDirectory ? removeTrailingSlash(conf.pageDirectory) : "routes"
3652
+ pageDirectory: conf.pageDirectory ? removeTrailingSlash(conf.pageDirectory) : "routes",
3653
+ validatePageLinks: conf.validatePageLinks ?? true
3652
3654
  };
3653
3655
  }
3654
3656
  loadConfig() {
@@ -3658,7 +3660,7 @@ var ConfigLoader = class {
3658
3660
  };
3659
3661
 
3660
3662
  // src/docs-plugin/search-indexer.ts
3661
- import chalk2 from "chalk";
3663
+ import chalk3 from "chalk";
3662
3664
  import { defined as defined2 } from "@qualcomm-ui/utils/guard";
3663
3665
 
3664
3666
  // src/docs-plugin/doc-props/doc-props-indexer.ts
@@ -4602,12 +4604,99 @@ var DocPropsIndexer = class {
4602
4604
  }
4603
4605
  };
4604
4606
 
4607
+ // src/docs-plugin/link-validator.ts
4608
+ import chalk2 from "chalk";
4609
+ import { posix } from "node:path";
4610
+ import { visit as visit11 } from "unist-util-visit";
4611
+ var externalPrefixes = ["https://", "http://", "mailto:", "tel:"];
4612
+ function isExternal(url) {
4613
+ return externalPrefixes.some((prefix) => url.startsWith(prefix));
4614
+ }
4615
+ function resolveLink(url, sourcePathname) {
4616
+ if (isExternal(url)) {
4617
+ return null;
4618
+ }
4619
+ const [rawPath, fragment] = url.split("#", 2);
4620
+ let pathname;
4621
+ if (!rawPath || rawPath === "./" || rawPath === ".") {
4622
+ pathname = sourcePathname;
4623
+ } else if (rawPath.startsWith("/")) {
4624
+ pathname = rawPath;
4625
+ } else {
4626
+ const sourceDir = sourcePathname.endsWith("/") ? sourcePathname : posix.dirname(sourcePathname);
4627
+ pathname = posix.resolve(sourceDir, rawPath);
4628
+ }
4629
+ if (pathname !== "/" && pathname.endsWith("/")) {
4630
+ pathname = pathname.slice(0, -1);
4631
+ }
4632
+ return { fragment: fragment || void 0, pathname };
4633
+ }
4634
+ function collectLinks(tree, sourceFile, sourcePathname) {
4635
+ const links = [];
4636
+ visit11(tree, "link", (node) => {
4637
+ const resolved = resolveLink(node.url, sourcePathname);
4638
+ if (!resolved) {
4639
+ return;
4640
+ }
4641
+ links.push({
4642
+ fragment: resolved.fragment,
4643
+ sourceFile,
4644
+ sourcePathname,
4645
+ targetPathname: resolved.pathname,
4646
+ url: node.url
4647
+ });
4648
+ });
4649
+ return links;
4650
+ }
4651
+ function validateLinks(links, pageMap, docPropIds) {
4652
+ const invalid = [];
4653
+ for (const link of links) {
4654
+ const page = pageMap[link.targetPathname];
4655
+ if (!page) {
4656
+ invalid.push({ ...link, reason: "page-not-found" });
4657
+ continue;
4658
+ }
4659
+ if (link.fragment) {
4660
+ const inToc = page.toc?.some((h) => h.id === link.fragment);
4661
+ const inDocProps = docPropIds?.[link.targetPathname]?.has(link.fragment);
4662
+ if (!inToc && !inDocProps) {
4663
+ invalid.push({ ...link, reason: "fragment-not-found" });
4664
+ }
4665
+ }
4666
+ }
4667
+ return invalid;
4668
+ }
4669
+ function reportInvalidLinks(invalidLinks) {
4670
+ if (invalidLinks.length === 0) {
4671
+ return;
4672
+ }
4673
+ const grouped = /* @__PURE__ */ new Map();
4674
+ for (const link of invalidLinks) {
4675
+ const existing = grouped.get(link.sourcePathname) ?? [];
4676
+ existing.push(link);
4677
+ grouped.set(link.sourcePathname, existing);
4678
+ }
4679
+ console.debug(
4680
+ `
4681
+ ${chalk2.yellowBright.bold(`Found ${invalidLinks.length} broken link${invalidLinks.length === 1 ? "" : "s"}:`)}`
4682
+ );
4683
+ for (const [sourcePathname, links] of grouped) {
4684
+ console.debug(`
4685
+ ${chalk2.blueBright.bold(sourcePathname)}`);
4686
+ for (const link of links) {
4687
+ const reason = link.reason === "page-not-found" ? "page not found" : `fragment "#${link.fragment}" not found`;
4688
+ console.debug(` ${chalk2.red("x")} ${link.url} \u2014 ${reason}`);
4689
+ }
4690
+ }
4691
+ console.debug("");
4692
+ }
4693
+
4605
4694
  // src/docs-plugin/markdown/knowledge/section-extractor.ts
4606
4695
  import { toString as toString2 } from "mdast-util-to-string";
4607
4696
  import remarkGfm2 from "remark-gfm";
4608
4697
  import remarkStringify4 from "remark-stringify";
4609
4698
  import { unified as unified4 } from "unified";
4610
- import { visit as visit11 } from "unist-util-visit";
4699
+ import { visit as visit12 } from "unist-util-visit";
4611
4700
 
4612
4701
  // src/docs-plugin/markdown/knowledge/utils.ts
4613
4702
  import { createHash as createHash2 } from "node:crypto";
@@ -4619,9 +4708,9 @@ function computeMd5(content) {
4619
4708
  // src/docs-plugin/markdown/knowledge/section-extractor.ts
4620
4709
  function transformLinks() {
4621
4710
  return () => (tree) => {
4622
- visit11(tree, "link", (node) => {
4711
+ visit12(tree, "link", (node) => {
4623
4712
  let text = "";
4624
- visit11(node, "text", (textNode) => {
4713
+ visit12(node, "text", (textNode) => {
4625
4714
  text += textNode.value;
4626
4715
  });
4627
4716
  Object.assign(node, {
@@ -5330,6 +5419,8 @@ var SearchIndexer = class {
5330
5419
  mdxFileReader;
5331
5420
  allowedHeadings;
5332
5421
  metaJson;
5422
+ _collectedLinks = [];
5423
+ _docPropIds = {};
5333
5424
  routeMetaNav = {};
5334
5425
  config;
5335
5426
  logWarnings;
@@ -5357,6 +5448,8 @@ var SearchIndexer = class {
5357
5448
  _searchIndex = [];
5358
5449
  reset() {
5359
5450
  this.mdxFileReader.reset();
5451
+ this._collectedLinks = [];
5452
+ this._docPropIds = {};
5360
5453
  this._pageMap = {};
5361
5454
  this._searchIndex = [];
5362
5455
  }
@@ -5467,6 +5560,11 @@ var SearchIndexer = class {
5467
5560
  removeMermaidCodeBlocks: true
5468
5561
  });
5469
5562
  const tree = processor.runSync(processor.parse(fileContents));
5563
+ if (this.config.validatePageLinks) {
5564
+ this._collectedLinks.push(
5565
+ ...collectLinks(tree, filePath, defaultSection.pathname)
5566
+ );
5567
+ }
5470
5568
  const pageInfo = {
5471
5569
  frontmatter,
5472
5570
  id: defaultSection.id,
@@ -5479,9 +5577,9 @@ var SearchIndexer = class {
5479
5577
  }
5480
5578
  } catch (error) {
5481
5579
  console.debug(
5482
- `${chalk2.yellowBright.bold(
5580
+ `${chalk3.yellowBright.bold(
5483
5581
  "Failed to parse mdx page content."
5484
- )} ${chalk2.blueBright.bold(filePath)}`
5582
+ )} ${chalk3.blueBright.bold(filePath)}`
5485
5583
  );
5486
5584
  if (this.config.throwOnError) {
5487
5585
  throw new Error(error);
@@ -5510,6 +5608,17 @@ var SearchIndexer = class {
5510
5608
  }
5511
5609
  if (docPropSections.length) {
5512
5610
  this._pageDocProps[defaultSection.pathname] = docProps;
5611
+ if (this.config.validatePageLinks) {
5612
+ const ids = /* @__PURE__ */ new Set();
5613
+ for (const section of docPropSections) {
5614
+ if (section.heading?.id) {
5615
+ ids.add(section.heading.id);
5616
+ }
5617
+ }
5618
+ if (ids.size) {
5619
+ this._docPropIds[defaultSection.pathname] = ids;
5620
+ }
5621
+ }
5513
5622
  }
5514
5623
  if (!cached) {
5515
5624
  this.mdxFileReader.updateCache(filePath, fileContents, {
@@ -5606,6 +5715,14 @@ var SearchIndexer = class {
5606
5715
  ).map((file) => this.compileTsxFile(file));
5607
5716
  this._searchIndex.push(...mdxIndex.filter((entry) => !entry.hideFromSearch));
5608
5717
  this.navBuilder.build();
5718
+ if (this.config.validatePageLinks) {
5719
+ const invalidLinks = validateLinks(
5720
+ this._collectedLinks,
5721
+ this._pageMap,
5722
+ this._docPropIds
5723
+ );
5724
+ reportInvalidLinks(invalidLinks);
5725
+ }
5609
5726
  return compiledFiles;
5610
5727
  }
5611
5728
  };