@qualcomm-ui/mdx-vite 2.5.0 → 2.5.2

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
@@ -5545,13 +5545,9 @@ import { dedent } from "@qualcomm-ui/utils/dedent";
5545
5545
  import { existsSync } from "node:fs";
5546
5546
  import { join as join2, resolve as resolve4 } from "node:path";
5547
5547
  function loadKnowledgeConfigFromEnv(options) {
5548
- const knowledgeId = process.env.KNOWLEDGE_ID;
5549
5548
  const exclude = options.exclude || (process.env.FILE_EXCLUDE_PATTERN ?? "").split(",");
5550
5549
  const outputPath = options.outputPath || process.env.KNOWLEDGE_OUTPUT_PATH;
5551
5550
  const prefix = process.env.PAGE_TITLE_PREFIX;
5552
- if (!knowledgeId) {
5553
- throw new Error("Missing required KNOWLEDGE_ID environment variable");
5554
- }
5555
5551
  if (!outputPath) {
5556
5552
  throw new Error("Missing required outputPath");
5557
5553
  }
@@ -5569,7 +5565,6 @@ function loadKnowledgeConfigFromEnv(options) {
5569
5565
  baseUrl: options.baseUrl || process.env.DOCS_SITE_BASE_URL,
5570
5566
  docPropsPath: resolvedConfig.typeDocProps,
5571
5567
  exclude,
5572
- knowledgeId,
5573
5568
  outputPath,
5574
5569
  pageTitlePrefix: prefix,
5575
5570
  routeDir
@@ -5580,95 +5575,6 @@ function loadKnowledgeConfigFromEnv(options) {
5580
5575
  async function exists(dirPath) {
5581
5576
  return access(dirPath).then(() => true).catch(() => false);
5582
5577
  }
5583
- async function loadDocProps(routesFolder, docPropsPath, verbose) {
5584
- const resolvedDocPropsPath = docPropsPath ? await exists(docPropsPath) ? docPropsPath : resolve5(process.cwd(), docPropsPath) : join3(dirname(routesFolder), "doc-props.json");
5585
- if (!await exists(resolvedDocPropsPath)) {
5586
- if (verbose) {
5587
- console.log(`Doc props file not found at: ${resolvedDocPropsPath}`);
5588
- }
5589
- return null;
5590
- }
5591
- try {
5592
- const content = await readFile(resolvedDocPropsPath, "utf-8");
5593
- const docProps = JSON.parse(content);
5594
- if (verbose) {
5595
- console.log(`Loaded doc props from: ${resolvedDocPropsPath}`);
5596
- console.log(`Found ${Object.keys(docProps.props).length} component types`);
5597
- }
5598
- return docProps;
5599
- } catch (error) {
5600
- if (verbose) {
5601
- console.log(`Error loading doc props: ${error}`);
5602
- }
5603
- return null;
5604
- }
5605
- }
5606
- function formatComment(comment) {
5607
- if (!comment) {
5608
- return "";
5609
- }
5610
- const parts = [];
5611
- if (comment.summary && comment.summary.length > 0) {
5612
- const summaryText = formatCommentParts(comment.summary);
5613
- if (summaryText.trim()) {
5614
- parts.push(summaryText.trim());
5615
- }
5616
- }
5617
- if (comment.blockTags && comment.blockTags.length > 0) {
5618
- for (const blockTag of comment.blockTags) {
5619
- const tagContent = formatCommentParts(blockTag.content);
5620
- if (tagContent.trim()) {
5621
- const tagName = blockTag.tag.replace("@", "");
5622
- if (tagName === "default" || tagName === "defaultValue") {
5623
- continue;
5624
- }
5625
- if (tagName === "example") {
5626
- parts.push(`**Example:**
5627
- \`\`\`
5628
- ${tagContent.trim()}
5629
- \`\`\``);
5630
- } else {
5631
- parts.push(`**${tagName}:** ${tagContent.trim()}`);
5632
- }
5633
- }
5634
- }
5635
- }
5636
- return parts.join("\n\n");
5637
- }
5638
- function formatCommentParts(parts) {
5639
- return parts.map((part) => {
5640
- switch (part.kind) {
5641
- case "text":
5642
- return part.text;
5643
- case "code":
5644
- const codeText = part.text.replace(/```\w*\n?/g, "").replace(/\n?```/g, "").trim();
5645
- if (codeText.includes("\n")) {
5646
- return `\`\`\`
5647
- ${codeText}
5648
- \`\`\``;
5649
- } else {
5650
- return codeText;
5651
- }
5652
- default:
5653
- if ("tag" in part && part.tag === "@link" && typeof part.target === "string") {
5654
- return `[${part.text}](${part.target})`;
5655
- }
5656
- return part.text;
5657
- }
5658
- }).join("").replace(/\n/g, " ").replace(/\s+/g, " ").trim();
5659
- }
5660
- function convertPropInfo(propInfo, isPartial, propType = void 0) {
5661
- return {
5662
- name: propInfo.name,
5663
- type: extractBestType(propInfo),
5664
- ...propInfo.defaultValue && {
5665
- defaultValue: cleanDefaultValue(propInfo.defaultValue)
5666
- },
5667
- description: formatComment(propInfo.comment || null),
5668
- propType,
5669
- required: extractRequired(propInfo, isPartial) || void 0
5670
- };
5671
- }
5672
5578
  function extractBestType(propInfo) {
5673
5579
  const type = propInfo.resolvedType?.prettyType || propInfo.type;
5674
5580
  return cleanType(type.startsWith("| ") ? type.substring(2) : type);
@@ -5682,133 +5588,20 @@ function cleanType(type) {
5682
5588
  function cleanDefaultValue(defaultValue) {
5683
5589
  return defaultValue.replace(/^\n+/, "").replace(/\n+$/, "").trim();
5684
5590
  }
5685
- async function scanPages(routesFolder, verbose, excludePatterns = [], baseUrl) {
5686
- const components = [];
5687
- function shouldExclude(fileOrDir) {
5688
- const dirName = basename(fileOrDir);
5689
- return excludePatterns.some((pattern) => {
5690
- if (pattern.includes("*")) {
5691
- const regex = new RegExp(`^${pattern.replace(/\*/g, ".*")}$`);
5692
- return regex.test(dirName);
5693
- }
5694
- return dirName === pattern;
5695
- });
5696
- }
5697
- async function scanDirectory(dirPath) {
5698
- if (shouldExclude(dirPath)) {
5699
- if (verbose) {
5700
- console.log(`Excluding directory: ${basename(dirPath)}`);
5701
- }
5702
- return;
5703
- }
5704
- const entries = await readdir(dirPath, { withFileTypes: true });
5705
- const mdxFiles = entries.filter(
5706
- (f) => f.name.endsWith(".mdx") && !shouldExclude(f.name)
5707
- ) ?? [];
5708
- for (const mdxFile of mdxFiles) {
5709
- const demosFolder = entries.find((f) => f.name === "demos");
5710
- const demosFolderPath = demosFolder ? join3(dirPath, demosFolder.name) : void 0;
5711
- const segments = getPathSegmentsFromFileName(
5712
- join3(dirPath, mdxFile.name),
5713
- routesFolder
5714
- );
5715
- const url = getPathnameFromPathSegments(segments);
5716
- components.push({
5717
- demosFolder: demosFolderPath,
5718
- id: segments.join("-").trim(),
5719
- mdxFile: join3(dirPath, mdxFile.name),
5720
- name: segments.at(-1),
5721
- path: dirPath,
5722
- url: baseUrl ? new URL(url, baseUrl).toString() : void 0
5723
- });
5724
- if (verbose) {
5725
- console.log(`Found component: ${basename(dirPath)}`);
5726
- console.log(` Demos folder: ${demosFolderPath || "NOT FOUND"}`);
5727
- }
5728
- }
5729
- for (const entry of entries) {
5730
- const fullPath = join3(dirPath, entry.name);
5731
- const stats = await stat(fullPath);
5732
- if (stats.isDirectory()) {
5733
- await scanDirectory(fullPath);
5734
- }
5735
- }
5736
- }
5737
- await scanDirectory(routesFolder);
5738
- return components;
5739
- }
5740
5591
  function isPreviewLine(trimmedLine) {
5741
5592
  return trimmedLine === "// preview" || /^\{\s*\/\*\s*preview\s*\*\/\s*\}$/.test(trimmedLine) || /^<!--\s*preview\s*-->$/.test(trimmedLine);
5742
5593
  }
5743
- function extractProps(props, isPartial) {
5744
- const propsInfo = [];
5745
- if (props.props?.length) {
5746
- propsInfo.push(
5747
- ...props.props.map((prop) => convertPropInfo(prop, isPartial))
5748
- );
5749
- }
5750
- if (props.input?.length) {
5751
- propsInfo.push(
5752
- ...props.input.map((prop) => convertPropInfo(prop, isPartial, "input"))
5753
- );
5754
- }
5755
- if (props.output?.length) {
5756
- propsInfo.push(
5757
- ...props.output.map((prop) => convertPropInfo(prop, isPartial, "output"))
5758
- );
5759
- }
5760
- return propsInfo;
5761
- }
5762
5594
  function removePreviewLines(code) {
5763
5595
  return code.split("\n").filter((line) => !isPreviewLine(line.trim())).join("\n");
5764
5596
  }
5765
- function getIntroLines(pages, projectName, description, baseUrl) {
5597
+ function getIntroLines(projectName, description) {
5766
5598
  const lines = [];
5767
5599
  if (projectName) {
5768
5600
  lines.push(`# ${projectName}`);
5769
- lines.push("");
5770
5601
  }
5771
5602
  if (description) {
5772
- lines.push(`> ${description}`);
5773
- lines.push("");
5774
- }
5775
- lines.push("## Components and Integrations");
5776
- lines.push("");
5777
- for (const page of pages) {
5778
- const url = baseUrl ? `${baseUrl}/${kebabCase(page.title)}` : `#${kebabCase(page.title)}`;
5779
- if (page.title.includes("Introduction")) {
5780
- lines.push(`- [${page.title}](${url}): introduction and getting started`);
5781
- } else if (page.title.includes("Tailwind")) {
5782
- lines.push(
5783
- `- [${page.title}](${url}): integration documentation and examples`
5784
- );
5785
- } else {
5786
- lines.push(
5787
- `- [${page.title}](${url}): component documentation and examples`
5788
- );
5789
- }
5790
- }
5791
- return lines.join("\n");
5792
- }
5793
- async function generateLlmsTxt(pages, projectName, description, baseUrl) {
5794
- const lines = [
5795
- getIntroLines(pages, projectName, description, baseUrl)
5796
- ];
5797
- lines.push("");
5798
- for (const page of pages) {
5799
- const content = page.content.split("\n").map((line) => {
5800
- if (line.startsWith("#")) {
5801
- return `#${line}`;
5802
- }
5803
- return line;
5804
- });
5805
- if (content.every((line) => !line.trim())) {
5806
- continue;
5807
- }
5808
- lines.push(`## ${page.title}`);
5809
- lines.push("");
5810
- lines.push(content.join("\n"));
5811
5603
  lines.push("");
5604
+ lines.push(`> ${description}`);
5812
5605
  }
5813
5606
  return lines.join("\n");
5814
5607
  }
@@ -5839,44 +5632,11 @@ async function resolveModulePath(importPath, fromFile) {
5839
5632
  }
5840
5633
  return null;
5841
5634
  }
5842
- async function collectRelativeImports(filePath, visited = /* @__PURE__ */ new Set(), verbose) {
5843
- const normalizedPath = resolve5(filePath);
5844
- if (visited.has(normalizedPath)) {
5845
- return [];
5846
- }
5847
- visited.add(normalizedPath);
5848
- const modules = [];
5849
- try {
5850
- const content = await readFile(normalizedPath, "utf-8");
5851
- const relativeImports = extractRelativeImports(content);
5852
- for (const importPath of relativeImports) {
5853
- const resolvedPath = await resolveModulePath(importPath, normalizedPath);
5854
- if (!resolvedPath) {
5855
- if (verbose) {
5856
- console.log(
5857
- ` Could not resolve import: ${importPath} from ${normalizedPath}`
5858
- );
5859
- }
5860
- continue;
5861
- }
5862
- const importContent = await readFile(resolvedPath, "utf-8");
5863
- modules.push({
5864
- content: importContent,
5865
- path: resolvedPath
5866
- });
5867
- const nestedModules = await collectRelativeImports(
5868
- resolvedPath,
5869
- visited,
5870
- verbose
5871
- );
5872
- modules.push(...nestedModules);
5873
- }
5874
- } catch (error) {
5875
- if (verbose) {
5876
- console.log(` Error processing ${normalizedPath}: ${error}`);
5877
- }
5878
- }
5879
- return modules;
5635
+ function extractMetadata(metadata) {
5636
+ return (metadata ?? []).map((current) => {
5637
+ const [key, value] = current.split("=");
5638
+ return [key, value];
5639
+ });
5880
5640
  }
5881
5641
  var replaceNpmInstallTabs = () => {
5882
5642
  return (tree, _file, done) => {
@@ -5897,376 +5657,606 @@ var replaceNpmInstallTabs = () => {
5897
5657
  done();
5898
5658
  };
5899
5659
  };
5900
- function replaceTypeDocProps(docProps, verbose) {
5901
- return () => (tree, _file, done) => {
5902
- visit7(
5903
- tree,
5904
- "mdxJsxFlowElement",
5905
- (node, index, parent) => {
5906
- if (node?.name !== "TypeDocProps") {
5907
- return;
5660
+ var KnowledgeGenerator = class {
5661
+ config;
5662
+ docProps = null;
5663
+ constructor(config2) {
5664
+ this.config = config2;
5665
+ }
5666
+ async run() {
5667
+ const extractedMetadata = extractMetadata(this.config.metadata);
5668
+ if (this.config.verbose) {
5669
+ console.log(`Scanning pages in: ${this.config.routeDir}`);
5670
+ if (this.config.exclude?.length) {
5671
+ console.log(`Excluding patterns: ${this.config.exclude.join(", ")}`);
5672
+ }
5673
+ }
5674
+ const [docProps, pages] = await Promise.all([
5675
+ this.loadDocProps(),
5676
+ this.scanPages()
5677
+ ]);
5678
+ this.docProps = docProps;
5679
+ if (pages.length === 0) {
5680
+ console.log("No pages found.");
5681
+ return;
5682
+ }
5683
+ if (this.config.verbose) {
5684
+ console.log(`Found ${pages.length} page(s)`);
5685
+ }
5686
+ const processedPages = [];
5687
+ for (const page of pages) {
5688
+ try {
5689
+ const processed = await this.processComponent(page);
5690
+ processedPages.push(processed);
5691
+ } catch (error) {
5692
+ console.error(`Failed to process page: ${page.name}`);
5693
+ process.exit(1);
5694
+ }
5695
+ }
5696
+ if (this.config.clean) {
5697
+ await rm(this.config.outputPath, { force: true, recursive: true }).catch(
5698
+ () => {
5908
5699
  }
5909
- const nameAttr = node.attributes?.find(
5910
- (attr) => attr.type === "mdxJsxAttribute" && attr.name === "name"
5700
+ );
5701
+ }
5702
+ if (this.config.outputMode === "aggregated") {
5703
+ await this.generateAggregatedOutput(processedPages, pages);
5704
+ } else {
5705
+ await mkdir2(this.config.outputPath, { recursive: true }).catch(() => {
5706
+ });
5707
+ await this.generatePerPageExports(
5708
+ pages,
5709
+ processedPages,
5710
+ extractedMetadata
5711
+ );
5712
+ }
5713
+ }
5714
+ async loadDocProps() {
5715
+ const resolvedDocPropsPath = this.config.docPropsPath ? await exists(this.config.docPropsPath) ? this.config.docPropsPath : resolve5(process.cwd(), this.config.docPropsPath) : join3(dirname(this.config.routeDir), "doc-props.json");
5716
+ if (!await exists(resolvedDocPropsPath)) {
5717
+ if (this.config.verbose) {
5718
+ console.log(`Doc props file not found at: ${resolvedDocPropsPath}`);
5719
+ }
5720
+ return null;
5721
+ }
5722
+ try {
5723
+ const content = await readFile(resolvedDocPropsPath, "utf-8");
5724
+ const docProps = JSON.parse(content);
5725
+ if (this.config.verbose) {
5726
+ console.log(`Loaded doc props from: ${resolvedDocPropsPath}`);
5727
+ console.log(
5728
+ `Found ${Object.keys(docProps.props).length} component types`
5911
5729
  );
5912
- const isPartial = node.attributes?.some(
5913
- (attr) => attr.type === "mdxJsxAttribute" && attr.name === "partial"
5730
+ }
5731
+ return docProps;
5732
+ } catch (error) {
5733
+ if (this.config.verbose) {
5734
+ console.log(`Error loading doc props: ${error}`);
5735
+ }
5736
+ return null;
5737
+ }
5738
+ }
5739
+ async scanPages() {
5740
+ const components = [];
5741
+ const excludePatterns = this.config.exclude ?? [];
5742
+ const shouldExclude = (fileOrDir) => {
5743
+ const dirName = basename(fileOrDir);
5744
+ return excludePatterns.some((pattern) => {
5745
+ if (pattern.includes("*")) {
5746
+ const regex = new RegExp(`^${pattern.replace(/\*/g, ".*")}$`);
5747
+ return regex.test(dirName);
5748
+ }
5749
+ return dirName === pattern;
5750
+ });
5751
+ };
5752
+ const scanDirectory = async (dirPath) => {
5753
+ if (shouldExclude(dirPath)) {
5754
+ if (this.config.verbose) {
5755
+ console.log(`Excluding directory: ${basename(dirPath)}`);
5756
+ }
5757
+ return;
5758
+ }
5759
+ const entries = await readdir(dirPath, { withFileTypes: true });
5760
+ const mdxFiles = entries.filter(
5761
+ (f) => f.name.endsWith(".mdx") && !shouldExclude(f.name)
5762
+ ) ?? [];
5763
+ for (const mdxFile of mdxFiles) {
5764
+ const demosFolder = entries.find((f) => f.name === "demos");
5765
+ const demosFolderPath = demosFolder ? join3(dirPath, demosFolder.name) : void 0;
5766
+ const segments = getPathSegmentsFromFileName(
5767
+ join3(dirPath, mdxFile.name),
5768
+ this.config.routeDir
5914
5769
  );
5915
- if (!docProps || !nameAttr) {
5916
- if (parent && index !== void 0) {
5917
- parent.children.splice(index, 1);
5918
- }
5919
- return;
5770
+ const url = getPathnameFromPathSegments(segments);
5771
+ components.push({
5772
+ demosFolder: demosFolderPath,
5773
+ id: segments.join("-").trim(),
5774
+ mdxFile: join3(dirPath, mdxFile.name),
5775
+ name: segments.at(-1),
5776
+ path: dirPath,
5777
+ url: this.config.baseUrl ? new URL(url, this.config.baseUrl).toString() : void 0
5778
+ });
5779
+ if (this.config.verbose) {
5780
+ console.log(`Found component: ${basename(dirPath)}`);
5781
+ console.log(` Demos folder: ${demosFolderPath || "NOT FOUND"}`);
5920
5782
  }
5921
- const propsNames = extractNamesFromAttribute(nameAttr);
5922
- if (propsNames.length === 0) {
5923
- if (parent && index !== void 0) {
5924
- parent.children.splice(index, 1);
5925
- }
5926
- return;
5783
+ }
5784
+ for (const entry of entries) {
5785
+ const fullPath = join3(dirPath, entry.name);
5786
+ const stats = await stat(fullPath);
5787
+ if (stats.isDirectory()) {
5788
+ await scanDirectory(fullPath);
5927
5789
  }
5928
- const propsName = propsNames[0];
5929
- const componentProps = docProps.props[propsName];
5930
- if (!componentProps) {
5931
- if (verbose) {
5932
- console.log(` TypeDocProps not found: ${propsName}`);
5933
- }
5934
- if (parent && index !== void 0) {
5935
- parent.children.splice(index, 1);
5790
+ }
5791
+ };
5792
+ await scanDirectory(this.config.routeDir);
5793
+ return components;
5794
+ }
5795
+ async collectRelativeImports(filePath, visited = /* @__PURE__ */ new Set()) {
5796
+ const normalizedPath = resolve5(filePath);
5797
+ if (visited.has(normalizedPath)) {
5798
+ return [];
5799
+ }
5800
+ visited.add(normalizedPath);
5801
+ const modules = [];
5802
+ try {
5803
+ const content = await readFile(normalizedPath, "utf-8");
5804
+ const relativeImports = extractRelativeImports(content);
5805
+ for (const importPath of relativeImports) {
5806
+ const resolvedPath = await resolveModulePath(importPath, normalizedPath);
5807
+ if (!resolvedPath) {
5808
+ if (this.config.verbose) {
5809
+ console.log(
5810
+ ` Could not resolve import: ${importPath} from ${normalizedPath}`
5811
+ );
5936
5812
  }
5937
- return;
5938
- }
5939
- const propsDoc = extractProps(componentProps, Boolean(isPartial));
5940
- if (verbose) {
5941
- console.log(
5942
- ` Replaced TypeDocProps ${propsName} with API documentation`
5943
- );
5813
+ continue;
5944
5814
  }
5945
- Object.assign(node, {
5946
- lang: "json",
5947
- meta: null,
5948
- type: "code",
5949
- value: JSON.stringify(propsDoc, null, 2)
5815
+ const importContent = await readFile(resolvedPath, "utf-8");
5816
+ modules.push({
5817
+ content: importContent,
5818
+ path: resolvedPath
5950
5819
  });
5820
+ const nestedModules = await this.collectRelativeImports(
5821
+ resolvedPath,
5822
+ visited
5823
+ );
5824
+ modules.push(...nestedModules);
5951
5825
  }
5952
- );
5953
- done();
5954
- };
5955
- }
5956
- function replaceDemos(demosFolder, verbose, demoFiles) {
5957
- return () => async (tree) => {
5958
- const promises = [];
5959
- visit7(
5960
- tree,
5961
- "mdxJsxFlowElement",
5962
- (node, index, parent) => {
5963
- if (!node?.name || !["QdsDemo", "CodeDemo", "Demo"].includes(node.name)) {
5964
- return;
5826
+ } catch (error) {
5827
+ if (this.config.verbose) {
5828
+ console.log(` Error processing ${normalizedPath}: ${error}`);
5829
+ }
5830
+ }
5831
+ return modules;
5832
+ }
5833
+ extractProps(props, isPartial) {
5834
+ const propsInfo = [];
5835
+ if (props.props?.length) {
5836
+ propsInfo.push(
5837
+ ...props.props.map((prop) => this.convertPropInfo(prop, isPartial))
5838
+ );
5839
+ }
5840
+ if (props.input?.length) {
5841
+ propsInfo.push(
5842
+ ...props.input.map(
5843
+ (prop) => this.convertPropInfo(prop, isPartial, "input")
5844
+ )
5845
+ );
5846
+ }
5847
+ if (props.output?.length) {
5848
+ propsInfo.push(
5849
+ ...props.output.map(
5850
+ (prop) => this.convertPropInfo(prop, isPartial, "output")
5851
+ )
5852
+ );
5853
+ }
5854
+ return propsInfo;
5855
+ }
5856
+ formatComment(comment) {
5857
+ if (!comment) {
5858
+ return "";
5859
+ }
5860
+ const parts = [];
5861
+ if (comment.summary && comment.summary.length > 0) {
5862
+ const summaryText = this.formatCommentParts(comment.summary);
5863
+ if (summaryText.trim()) {
5864
+ parts.push(summaryText.trim());
5865
+ }
5866
+ }
5867
+ if (comment.blockTags && comment.blockTags.length > 0) {
5868
+ for (const blockTag of comment.blockTags) {
5869
+ const tagContent = this.formatCommentParts(blockTag.content);
5870
+ if (tagContent.trim()) {
5871
+ const tagName = blockTag.tag.replace("@", "");
5872
+ if (tagName === "default" || tagName === "defaultValue") {
5873
+ continue;
5874
+ }
5875
+ if (tagName === "example") {
5876
+ parts.push(`**Example:**
5877
+ \`\`\`
5878
+ ${tagContent.trim()}
5879
+ \`\`\``);
5880
+ } else {
5881
+ parts.push(`**${tagName}:** ${tagContent.trim()}`);
5882
+ }
5965
5883
  }
5966
- const nameAttr = node.attributes?.find(
5967
- (attr) => attr.type === "mdxJsxAttribute" && attr.name === "name"
5968
- );
5969
- const nodeAttr = node.attributes?.find(
5970
- (attr) => attr.type === "mdxJsxAttribute" && attr.name === "node"
5971
- );
5972
- let demoName;
5973
- if (nameAttr && typeof nameAttr.value === "string") {
5974
- demoName = nameAttr.value;
5975
- } else if (nodeAttr?.value && typeof nodeAttr.value !== "string") {
5976
- const estree = nodeAttr.value.data?.estree;
5977
- if (estree?.body?.[0]?.type === "ExpressionStatement") {
5978
- const expression = estree.body[0].expression;
5979
- if (expression.type === "MemberExpression" && expression.object.type === "Identifier" && expression.object.name === "Demo" && expression.property.type === "Identifier") {
5980
- demoName = expression.property.name;
5884
+ }
5885
+ }
5886
+ return parts.join("\n\n");
5887
+ }
5888
+ formatCommentParts(parts) {
5889
+ return parts.map((part) => {
5890
+ switch (part.kind) {
5891
+ case "text":
5892
+ return part.text;
5893
+ case "code":
5894
+ const codeText = part.text.replace(/```\w*\n?/g, "").replace(/\n?```/g, "").trim();
5895
+ if (codeText.includes("\n")) {
5896
+ return `\`\`\`
5897
+ ${codeText}
5898
+ \`\`\``;
5899
+ } else {
5900
+ return codeText;
5901
+ }
5902
+ default:
5903
+ if (this.config.outputMode === "per-page" && "tag" in part && part.tag === "@link" && typeof part.target === "string") {
5904
+ return `[${part.text}](${part.target})`;
5905
+ }
5906
+ return part.text;
5907
+ }
5908
+ }).join("").replace(/\n/g, " ").replace(/\s+/g, " ").trim();
5909
+ }
5910
+ convertPropInfo(propInfo, isPartial, propType = void 0) {
5911
+ return {
5912
+ name: propInfo.name,
5913
+ type: extractBestType(propInfo),
5914
+ ...propInfo.defaultValue && {
5915
+ defaultValue: cleanDefaultValue(propInfo.defaultValue)
5916
+ },
5917
+ description: this.formatComment(propInfo.comment || null),
5918
+ propType,
5919
+ required: extractRequired(propInfo, isPartial) || void 0
5920
+ };
5921
+ }
5922
+ /**
5923
+ * Creates a remark plugin that replaces TypeDocProps JSX elements with JSON
5924
+ * code blocks containing component prop documentation.
5925
+ */
5926
+ replaceTypeDocProps() {
5927
+ return () => (tree, _file, done) => {
5928
+ visit7(
5929
+ tree,
5930
+ "mdxJsxFlowElement",
5931
+ (node, index, parent) => {
5932
+ if (node?.name !== "TypeDocProps") {
5933
+ return;
5934
+ }
5935
+ const nameAttr = node.attributes?.find(
5936
+ (attr) => attr.type === "mdxJsxAttribute" && attr.name === "name"
5937
+ );
5938
+ const isPartial = node.attributes?.some(
5939
+ (attr) => attr.type === "mdxJsxAttribute" && attr.name === "partial"
5940
+ );
5941
+ if (!this.docProps || !nameAttr) {
5942
+ if (parent && index !== void 0) {
5943
+ parent.children.splice(index, 1);
5981
5944
  }
5945
+ return;
5982
5946
  }
5983
- }
5984
- if (!demoName) {
5985
- if (parent && index !== void 0) {
5986
- parent.children.splice(index, 1);
5947
+ const propsNames = extractNamesFromAttribute(nameAttr);
5948
+ if (propsNames.length === 0) {
5949
+ if (parent && index !== void 0) {
5950
+ parent.children.splice(index, 1);
5951
+ }
5952
+ return;
5987
5953
  }
5988
- return;
5954
+ const propsName = propsNames[0];
5955
+ const componentProps = this.docProps.props[propsName];
5956
+ if (!componentProps) {
5957
+ if (this.config.verbose) {
5958
+ console.log(` TypeDocProps not found: ${propsName}`);
5959
+ }
5960
+ if (parent && index !== void 0) {
5961
+ parent.children.splice(index, 1);
5962
+ }
5963
+ return;
5964
+ }
5965
+ const propsDoc = this.extractProps(componentProps, Boolean(isPartial));
5966
+ if (this.config.verbose) {
5967
+ console.log(
5968
+ ` Replaced TypeDocProps ${propsName} with API documentation`
5969
+ );
5970
+ }
5971
+ Object.assign(node, {
5972
+ lang: "json",
5973
+ meta: null,
5974
+ type: "code",
5975
+ value: JSON.stringify(propsDoc, null, 2)
5976
+ });
5989
5977
  }
5990
- promises.push(
5991
- (async () => {
5992
- const kebabName = kebabCase(demoName);
5993
- let filePath = `${kebabName}.tsx`;
5994
- if (!demosFolder) {
5995
- if (verbose) {
5996
- console.log(` No demos folder for ${demoName}`);
5997
- }
5998
- if (parent && index !== void 0) {
5999
- parent.children.splice(index, 1);
5978
+ );
5979
+ done();
5980
+ };
5981
+ }
5982
+ /**
5983
+ * Creates a remark plugin that replaces demo JSX elements (QdsDemo, CodeDemo,
5984
+ * Demo) with code blocks containing the demo source code from the demos folder.
5985
+ */
5986
+ replaceDemos(demosFolder, demoFiles) {
5987
+ return () => async (tree) => {
5988
+ const promises = [];
5989
+ visit7(
5990
+ tree,
5991
+ "mdxJsxFlowElement",
5992
+ (node, index, parent) => {
5993
+ if (!node?.name || !["QdsDemo", "CodeDemo", "Demo"].includes(node.name)) {
5994
+ return;
5995
+ }
5996
+ const nameAttr = node.attributes?.find(
5997
+ (attr) => attr.type === "mdxJsxAttribute" && attr.name === "name"
5998
+ );
5999
+ const nodeAttr = node.attributes?.find(
6000
+ (attr) => attr.type === "mdxJsxAttribute" && attr.name === "node"
6001
+ );
6002
+ let demoName;
6003
+ if (nameAttr && typeof nameAttr.value === "string") {
6004
+ demoName = nameAttr.value;
6005
+ } else if (nodeAttr?.value && typeof nodeAttr.value !== "string") {
6006
+ const estree = nodeAttr.value.data?.estree;
6007
+ if (estree?.body?.[0]?.type === "ExpressionStatement") {
6008
+ const expression = estree.body[0].expression;
6009
+ if (expression.type === "MemberExpression" && expression.object.type === "Identifier" && expression.object.name === "Demo" && expression.property.type === "Identifier") {
6010
+ demoName = expression.property.name;
6000
6011
  }
6001
- return;
6002
6012
  }
6003
- let demoFilePath = join3(demosFolder, filePath);
6004
- let isAngularDemo = false;
6005
- if (!await exists(demoFilePath)) {
6006
- demoFilePath = join3(demosFolder, `${kebabName}.ts`);
6007
- if (await exists(demoFilePath)) {
6008
- isAngularDemo = true;
6009
- filePath = `${kebabCase(demoName).replace("-component", ".component")}.ts`;
6010
- demoFilePath = join3(demosFolder, filePath);
6011
- } else {
6012
- console.log(` Demo not found ${demoName}`);
6013
+ }
6014
+ if (!demoName) {
6015
+ if (parent && index !== void 0) {
6016
+ parent.children.splice(index, 1);
6017
+ }
6018
+ return;
6019
+ }
6020
+ promises.push(
6021
+ (async () => {
6022
+ const kebabName = kebabCase(demoName);
6023
+ let filePath = `${kebabName}.tsx`;
6024
+ if (!demosFolder) {
6025
+ if (this.config.verbose) {
6026
+ console.log(` No demos folder for ${demoName}`);
6027
+ }
6013
6028
  if (parent && index !== void 0) {
6014
6029
  parent.children.splice(index, 1);
6015
6030
  }
6016
6031
  return;
6017
6032
  }
6018
- }
6019
- try {
6020
- const demoCode = await readFile(demoFilePath, "utf-8");
6021
- const cleanedCode = removePreviewLines(demoCode);
6022
- if (verbose) {
6023
- console.log(` Replaced demo ${demoName} with source code`);
6024
- }
6025
- demoFiles.push(demoFilePath);
6026
- Object.assign(node, {
6027
- lang: isAngularDemo ? "angular-ts" : "tsx",
6028
- meta: null,
6029
- type: "code",
6030
- value: cleanedCode
6031
- });
6032
- } catch (error) {
6033
- if (verbose) {
6034
- console.log(` Error reading demo ${demoName}: ${error}`);
6033
+ let demoFilePath = join3(demosFolder, filePath);
6034
+ let isAngularDemo = false;
6035
+ if (!await exists(demoFilePath)) {
6036
+ demoFilePath = join3(demosFolder, `${kebabName}.ts`);
6037
+ if (await exists(demoFilePath)) {
6038
+ isAngularDemo = true;
6039
+ filePath = `${kebabCase(demoName).replace("-component", ".component")}.ts`;
6040
+ demoFilePath = join3(demosFolder, filePath);
6041
+ } else {
6042
+ console.log(` Demo not found ${demoName}`);
6043
+ if (parent && index !== void 0) {
6044
+ parent.children.splice(index, 1);
6045
+ }
6046
+ return;
6047
+ }
6035
6048
  }
6036
- if (parent && index !== void 0) {
6037
- parent.children.splice(index, 1);
6049
+ try {
6050
+ const demoCode = await readFile(demoFilePath, "utf-8");
6051
+ const cleanedCode = removePreviewLines(demoCode);
6052
+ if (this.config.verbose) {
6053
+ console.log(` Replaced demo ${demoName} with source code`);
6054
+ }
6055
+ demoFiles.push(demoFilePath);
6056
+ Object.assign(node, {
6057
+ lang: isAngularDemo ? "angular-ts" : "tsx",
6058
+ meta: null,
6059
+ type: "code",
6060
+ value: cleanedCode
6061
+ });
6062
+ } catch (error) {
6063
+ if (this.config.verbose) {
6064
+ console.log(` Error reading demo ${demoName}: ${error}`);
6065
+ }
6066
+ if (parent && index !== void 0) {
6067
+ parent.children.splice(index, 1);
6068
+ }
6038
6069
  }
6039
- }
6040
- })()
6041
- );
6042
- }
6043
- );
6044
- await Promise.all(promises);
6045
- };
6046
- }
6047
- async function processMdxContent(mdxContent, pageUrl, demosFolder, docProps, verbose) {
6048
- const demoFiles = [];
6049
- let processedContent = mdxContent;
6050
- const lines = processedContent.split("\n");
6051
- const titleLine = lines.findIndex((line) => line.startsWith("# "));
6052
- processedContent = titleLine >= 0 ? lines.slice(titleLine + 1).join("\n") : processedContent;
6053
- if (pageUrl) {
6070
+ })()
6071
+ );
6072
+ }
6073
+ );
6074
+ await Promise.all(promises);
6075
+ };
6076
+ }
6077
+ /**
6078
+ * Processes MDX content by transforming JSX elements (TypeDocProps, demos)
6079
+ * into markdown, resolving relative links, and cleaning up formatting.
6080
+ */
6081
+ async processMdxContent(mdxContent, pageUrl, demosFolder) {
6082
+ const demoFiles = [];
6083
+ let processedContent = mdxContent;
6084
+ const lines = processedContent.split("\n");
6085
+ const titleLine = lines.findIndex((line) => line.startsWith("# "));
6086
+ processedContent = titleLine >= 0 ? lines.slice(titleLine + 1).join("\n") : processedContent;
6054
6087
  processedContent = processedContent.replace(
6055
6088
  /\[([^\]]+)\]\(\.\/#([^)]+)\)/g,
6056
- (_, text, anchor) => `[${text}](${pageUrl}#${anchor})`
6089
+ (_, text, anchor) => pageUrl && this.config.outputMode === "per-page" ? `[${text}](${pageUrl}#${anchor})` : text
6057
6090
  );
6091
+ const processor = unified4().use(remarkParse4).use(remarkMdx3).use(this.replaceTypeDocProps()).use(this.replaceDemos(demosFolder, demoFiles)).use(remarkStringify3);
6092
+ const processed = await processor.process(processedContent);
6093
+ processedContent = String(processed);
6094
+ processedContent = processedContent.replace(/\n\s*\n\s*\n/g, "\n\n");
6095
+ return { content: processedContent, demoFiles };
6058
6096
  }
6059
- const processor = unified4().use(remarkParse4).use(remarkMdx3).use(replaceTypeDocProps(docProps, verbose)).use(replaceDemos(demosFolder, verbose, demoFiles)).use(remarkStringify3);
6060
- const processed = await processor.process(processedContent);
6061
- processedContent = String(processed);
6062
- processedContent = processedContent.replace(/\n\s*\n\s*\n/g, "\n\n");
6063
- return { content: processedContent, demoFiles };
6064
- }
6065
- async function processComponent(component, docProps, verbose) {
6066
- try {
6067
- const mdxContent = await readFile(component.mdxFile, "utf-8");
6068
- if (verbose) {
6069
- console.log(`Processing page: ${component.name}`);
6097
+ async processComponent(component) {
6098
+ try {
6099
+ const mdxContent = await readFile(component.mdxFile, "utf-8");
6100
+ if (this.config.verbose) {
6101
+ console.log(`Processing page: ${component.name}`);
6102
+ }
6103
+ const processor = unified4().use(remarkParse4).use(remarkMdx3).use(replaceNpmInstallTabs).use(remarkFrontmatter3, ["yaml"]).use(remarkParseFrontmatter2);
6104
+ if (this.config.outputMode === "per-page") {
6105
+ processor.use(remarkSelfLinkHeadings(component.url));
6106
+ }
6107
+ processor.use(remarkStringify3);
6108
+ const parsed = await processor.process(mdxContent);
6109
+ const frontmatter = parsed.data?.frontmatter || {};
6110
+ const { content: processedContent, demoFiles } = await this.processMdxContent(
6111
+ String(parsed),
6112
+ component.url,
6113
+ component.demosFolder
6114
+ );
6115
+ const removeJsxProcessor = unified4().use(remarkParse4).use(remarkMdx3).use(remarkRemoveJsx).use(remarkStringify3);
6116
+ const removedJsx = String(
6117
+ await removeJsxProcessor.process(processedContent)
6118
+ );
6119
+ const contentWithoutFrontmatter = removedJsx.replace(
6120
+ /^---[\s\S]*?---\n/,
6121
+ ""
6122
+ );
6123
+ const title = frontmatter.title || component.name;
6124
+ return {
6125
+ content: contentWithoutFrontmatter.trim(),
6126
+ demoFiles,
6127
+ frontmatter,
6128
+ title,
6129
+ url: component.url
6130
+ };
6131
+ } catch (error) {
6132
+ console.error(`Error processing component ${component.name}:`, error);
6133
+ throw error;
6070
6134
  }
6071
- const processor = unified4().use(remarkParse4).use(remarkMdx3).use(replaceNpmInstallTabs).use(remarkFrontmatter3, ["yaml"]).use(remarkParseFrontmatter2).use(remarkSelfLinkHeadings(component.url)).use(remarkStringify3);
6072
- const parsed = await processor.process(mdxContent);
6073
- const frontmatter = parsed.data?.frontmatter || {};
6074
- const { content: processedContent, demoFiles } = await processMdxContent(
6075
- String(parsed),
6076
- component.url,
6077
- component.demosFolder,
6078
- docProps,
6079
- verbose
6080
- );
6081
- const removeJsxProcessor = unified4().use(remarkParse4).use(remarkMdx3).use(remarkRemoveJsx).use(remarkStringify3);
6082
- const removedJsx = String(
6083
- await removeJsxProcessor.process(processedContent)
6084
- );
6085
- const contentWithoutFrontmatter = removedJsx.replace(
6086
- /^---[\s\S]*?---\n/,
6087
- ""
6088
- );
6089
- const title = frontmatter.title || component.name;
6090
- return {
6091
- content: contentWithoutFrontmatter.trim(),
6092
- demoFiles,
6093
- frontmatter,
6094
- title
6095
- };
6096
- } catch (error) {
6097
- console.error(`Error processing component ${component.name}:`, error);
6098
- throw error;
6099
6135
  }
6100
- }
6101
- async function generatePerPageExports({
6102
- includeImports,
6103
- metadata,
6104
- outputPath,
6105
- pages,
6106
- pageTitlePrefix,
6107
- processedPages,
6108
- verbose
6109
- }) {
6110
- await mkdir2(dirname(outputPath), { recursive: true }).catch();
6111
- const count = processedPages.length;
6112
- let totalSize = 0;
6113
- await Promise.all(
6114
- processedPages.map(async (processedPage, index) => {
6115
- const page = pages[index];
6116
- const lines = [];
6117
- if (metadata.length || page.url) {
6118
- lines.push("---");
6119
- if (page.url) {
6120
- lines.push(`url: ${page.url}`);
6121
- }
6122
- if (metadata.length) {
6123
- for (const [key, value] of metadata) {
6124
- lines.push(`${key}: ${value}`);
6125
- }
6136
+ async generateLlmsTxt(pages) {
6137
+ const lines = [
6138
+ getIntroLines(this.config.name, this.config.description)
6139
+ ];
6140
+ lines.push("");
6141
+ for (const page of pages) {
6142
+ const content = page.content.split("\n").map((line) => {
6143
+ if (line.startsWith("#")) {
6144
+ return `#${line}`;
6126
6145
  }
6127
- lines.push("---");
6128
- lines.push("");
6146
+ return line;
6147
+ });
6148
+ if (content.every((line) => !line.trim())) {
6149
+ continue;
6129
6150
  }
6130
- lines.push(`# ${processedPage.title}`);
6151
+ lines.push(`## ${page.title}`);
6131
6152
  lines.push("");
6132
- if (processedPage.frontmatter?.title) {
6133
- page.name = processedPage.frontmatter.title;
6134
- }
6135
- let content = processedPage.content;
6136
- if (pageTitlePrefix) {
6137
- content = content.replace(
6138
- `# ${page.name}`,
6139
- `# ${pageTitlePrefix} ${page.name}`
6140
- );
6141
- page.name = `${pageTitlePrefix} ${page.name}`;
6142
- }
6143
- lines.push(content);
6153
+ lines.push(content.join("\n"));
6144
6154
  lines.push("");
6145
- if (includeImports && processedPage.demoFiles.length > 0) {
6146
- if (verbose) {
6147
- console.log(
6148
- `Collecting imports for ${page.name} from ${processedPage.demoFiles.length} demo files`
6149
- );
6155
+ }
6156
+ return lines.join("\n");
6157
+ }
6158
+ async generateAggregatedOutput(processedPages, pages) {
6159
+ const llmsTxtContent = await this.generateLlmsTxt(processedPages);
6160
+ await mkdir2(dirname(this.config.outputPath), { recursive: true }).catch(
6161
+ () => {
6162
+ }
6163
+ );
6164
+ await writeFile3(this.config.outputPath, llmsTxtContent, "utf-8");
6165
+ const outputStats = await stat(this.config.outputPath);
6166
+ const outputSizeKb = (outputStats.size / 1024).toFixed(1);
6167
+ console.log(
6168
+ `Generated ${this.config.outputPath} with ${pages.length} component(s) at: ${this.config.outputPath}`
6169
+ );
6170
+ console.log(`File size: ${outputSizeKb} KB`);
6171
+ }
6172
+ async generatePerPageExports(pages, processedPages, metadata) {
6173
+ await mkdir2(dirname(this.config.outputPath), { recursive: true }).catch(
6174
+ () => {
6175
+ }
6176
+ );
6177
+ const count = processedPages.length;
6178
+ let totalSize = 0;
6179
+ await Promise.all(
6180
+ processedPages.map(async (processedPage, index) => {
6181
+ const page = pages[index];
6182
+ const lines = [];
6183
+ if (metadata.length || page.url) {
6184
+ lines.push("---");
6185
+ if (page.url) {
6186
+ lines.push(`url: ${page.url}`);
6187
+ }
6188
+ if (metadata.length) {
6189
+ for (const [key, value] of metadata) {
6190
+ lines.push(`${key}: ${value}`);
6191
+ }
6192
+ }
6193
+ lines.push("---");
6194
+ lines.push("");
6150
6195
  }
6151
- const allImports = [];
6152
- for (const demoFile of processedPage.demoFiles) {
6153
- const imports = await collectRelativeImports(
6154
- demoFile,
6155
- /* @__PURE__ */ new Set(),
6156
- verbose
6196
+ lines.push(`# ${processedPage.title}`);
6197
+ lines.push("");
6198
+ if (processedPage.frontmatter?.title) {
6199
+ page.name = processedPage.frontmatter.title;
6200
+ }
6201
+ let content = processedPage.content;
6202
+ if (this.config.pageTitlePrefix) {
6203
+ content = content.replace(
6204
+ `# ${page.name}`,
6205
+ `# ${this.config.pageTitlePrefix} ${page.name}`
6157
6206
  );
6158
- allImports.push(...imports);
6207
+ page.name = `${this.config.pageTitlePrefix} ${page.name}`;
6159
6208
  }
6160
- const uniqueImports = Array.from(
6161
- new Map(allImports.map((m) => [m.path, m])).values()
6162
- );
6163
- if (verbose) {
6164
- console.log(
6165
- ` Collected ${uniqueImports.length} unique import modules`
6209
+ lines.push(content);
6210
+ lines.push("");
6211
+ if (this.config.includeImports && processedPage.demoFiles.length > 0) {
6212
+ if (this.config.verbose) {
6213
+ console.log(
6214
+ `Collecting imports for ${page.name} from ${processedPage.demoFiles.length} demo files`
6215
+ );
6216
+ }
6217
+ const allImports = [];
6218
+ for (const demoFile of processedPage.demoFiles) {
6219
+ const imports = await this.collectRelativeImports(
6220
+ demoFile,
6221
+ /* @__PURE__ */ new Set()
6222
+ );
6223
+ allImports.push(...imports);
6224
+ }
6225
+ const uniqueImports = Array.from(
6226
+ new Map(allImports.map((m) => [m.path, m])).values()
6166
6227
  );
6167
- }
6168
- if (uniqueImports.length > 0) {
6169
- lines.push("## Related Source Files");
6170
- lines.push("");
6171
- for (const importedModule of uniqueImports) {
6172
- const ext = extname(importedModule.path).slice(1);
6173
- lines.push(`### ${basename(importedModule.path)}`);
6174
- lines.push("");
6175
- lines.push(`\`\`\`${ext}`);
6176
- lines.push(importedModule.content);
6177
- lines.push("```");
6228
+ if (this.config.verbose) {
6229
+ console.log(
6230
+ ` Collected ${uniqueImports.length} unique import modules`
6231
+ );
6232
+ }
6233
+ if (uniqueImports.length > 0) {
6234
+ lines.push("## Related Source Files");
6178
6235
  lines.push("");
6236
+ for (const importedModule of uniqueImports) {
6237
+ const ext = extname(importedModule.path).slice(1);
6238
+ lines.push(`### ${basename(importedModule.path)}`);
6239
+ lines.push("");
6240
+ lines.push(`\`\`\`${ext}`);
6241
+ lines.push(importedModule.content);
6242
+ lines.push("```");
6243
+ lines.push("");
6244
+ }
6179
6245
  }
6180
6246
  }
6181
- }
6182
- const outfile = `${resolve5(outputPath)}/${kebabCase(page.id || page.name)}.md`;
6183
- await writeFile3(outfile, lines.join("\n"), "utf-8");
6184
- const stats = await stat(outfile);
6185
- totalSize += stats.size / 1024;
6186
- })
6187
- );
6188
- console.log(`Generated ${count} component(s) in ${outputPath}`);
6189
- console.log(`Folder size: ${totalSize.toFixed(1)} KB`);
6190
- }
6191
- function extractMetadata(metadata) {
6192
- return (metadata ?? []).map((current) => {
6193
- const [key, value] = current.split("=");
6194
- return [key, value];
6195
- });
6196
- }
6197
- async function generate({
6198
- baseUrl,
6199
- clean,
6200
- description,
6201
- docPropsPath,
6202
- exclude,
6203
- includeImports,
6204
- metadata,
6205
- name,
6206
- outputMode,
6207
- outputPath,
6208
- pageTitlePrefix,
6209
- routeDir,
6210
- verbose
6211
- }) {
6212
- const extractedMetadata = extractMetadata(metadata);
6213
- if (verbose) {
6214
- console.log(`Scanning pages in: ${routeDir}`);
6215
- if (exclude?.length) {
6216
- console.log(`Excluding patterns: ${exclude.join(", ")}`);
6217
- }
6218
- }
6219
- const [docProps, pages] = await Promise.all([
6220
- loadDocProps(routeDir, docPropsPath, verbose),
6221
- scanPages(routeDir, verbose, exclude, baseUrl)
6222
- ]);
6223
- if (pages.length === 0) {
6224
- console.log("No pages found.");
6225
- return;
6226
- }
6227
- if (verbose) {
6228
- console.log(`Found ${pages.length} page(s)`);
6229
- }
6230
- const processedPages = [];
6231
- for (const page of pages) {
6232
- try {
6233
- const processed = await processComponent(page, docProps, verbose);
6234
- processedPages.push(processed);
6235
- } catch (error) {
6236
- console.error(`Failed to process page: ${page.name}`);
6237
- process.exit(1);
6238
- }
6239
- }
6240
- if (clean) {
6241
- await rm(outputPath, { force: true, recursive: true }).catch();
6242
- }
6243
- if (outputMode === "aggregated") {
6244
- const llmsTxtContent = await generateLlmsTxt(
6245
- processedPages,
6246
- name,
6247
- description,
6248
- baseUrl
6249
- );
6250
- await mkdir2(dirname(outputPath), { recursive: true }).catch();
6251
- await writeFile3(outputPath, llmsTxtContent, "utf-8");
6252
- const outputStats = await stat(outputPath);
6253
- const outputSizeKb = (outputStats.size / 1024).toFixed(1);
6254
- console.log(
6255
- `Generated ${outputPath} with ${pages.length} component(s) at: ${outputPath}`
6247
+ const outfile = `${resolve5(this.config.outputPath)}/${kebabCase(page.id || page.name)}.md`;
6248
+ await writeFile3(outfile, lines.join("\n"), "utf-8");
6249
+ const stats = await stat(outfile);
6250
+ totalSize += stats.size / 1024;
6251
+ })
6256
6252
  );
6257
- console.log(`File size: ${outputSizeKb} KB`);
6258
- } else {
6259
- await mkdir2(outputPath, { recursive: true }).catch();
6260
- await generatePerPageExports({
6261
- includeImports,
6262
- metadata: extractedMetadata,
6263
- outputPath,
6264
- pages,
6265
- pageTitlePrefix,
6266
- processedPages,
6267
- verbose
6268
- });
6253
+ console.log(`Generated ${count} component(s) in ${this.config.outputPath}`);
6254
+ console.log(`Folder size: ${totalSize.toFixed(1)} KB`);
6269
6255
  }
6256
+ };
6257
+ async function generate(config2) {
6258
+ const generator = new KnowledgeGenerator(config2);
6259
+ await generator.run();
6270
6260
  }
6271
6261
  function addGenerateKnowledgeCommand() {
6272
6262
  program.description("Generate llms.txt from QUI Docs documentation").command("generate-llms-txt").option("-n, --name <name>", "Project name for llms.txt header").requiredOption("-m, --output-mode <outputMode>").option("-o, --outputPath <outputPath>", "Output file or directory.").option(
@@ -6288,14 +6278,7 @@ function addGenerateKnowledgeCommand() {
6288
6278
 
6289
6279
  // src/open-web-ui-knowledge/upload-knowledge.ts
6290
6280
  import { createHash as createHash2 } from "node:crypto";
6291
- import {
6292
- access as access2,
6293
- mkdir as mkdir3,
6294
- readdir as readdir2,
6295
- readFile as readFile2,
6296
- stat as stat2,
6297
- writeFile as writeFile4
6298
- } from "node:fs/promises";
6281
+ import { access as access2, readdir as readdir2, readFile as readFile2, stat as stat2 } from "node:fs/promises";
6299
6282
  import { resolve as resolve6 } from "node:path";
6300
6283
  import { setTimeout as setTimeout2 } from "node:timers/promises";
6301
6284
  import ora from "ora";
@@ -6306,10 +6289,66 @@ function calculateFileHash(fileData) {
6306
6289
  var Uploader = class {
6307
6290
  config;
6308
6291
  api;
6292
+ fileHashCache = /* @__PURE__ */ new Map();
6293
+ knowledgeFilesCache = null;
6309
6294
  constructor(config2) {
6310
6295
  this.config = config2;
6311
6296
  this.api = new KnowledgeApi(config2);
6312
6297
  }
6298
+ async buildHashCache(files) {
6299
+ const results = await Promise.allSettled(
6300
+ files.map(async (f) => {
6301
+ const data = await this.api.downloadFile(f.id);
6302
+ if (data) {
6303
+ this.fileHashCache.set(f.id, calculateFileHash(data));
6304
+ }
6305
+ })
6306
+ );
6307
+ const failures = results.filter((r) => r.status === "rejected");
6308
+ if (failures.length > 0) {
6309
+ console.warn(`Failed to cache ${failures.length} file hashes`);
6310
+ }
6311
+ }
6312
+ async waitForFileDeletion(fileId, fileName, maxAttempts = 15) {
6313
+ const spinner = ora(`File changed, deleting ${fileName}`).start();
6314
+ for (let i = 0; i < maxAttempts; i++) {
6315
+ this.knowledgeFilesCache = null;
6316
+ const knowledge = await this.api.listKnowledgeFiles();
6317
+ const stillExists = (knowledge.files ?? []).some((f) => f.id === fileId);
6318
+ if (!stillExists) {
6319
+ this.fileHashCache.delete(fileId);
6320
+ spinner.succeed(`File ${fileId} deleted`);
6321
+ return true;
6322
+ }
6323
+ await setTimeout2(100 * (i + 1));
6324
+ }
6325
+ spinner.stop();
6326
+ console.debug(`File ${fileId} may not have been fully deleted`);
6327
+ return false;
6328
+ }
6329
+ async uploadWithRetry(name, contents, maxRetries = 10) {
6330
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
6331
+ const result = await this.uploadFile(name, contents);
6332
+ if (result.success) {
6333
+ return result;
6334
+ }
6335
+ if (result.response?.detail?.includes("Duplicate content detected")) {
6336
+ console.warn(
6337
+ `Duplicate content: ${name} is already in knowledge base, skipping`
6338
+ );
6339
+ return { skipped: true, success: true };
6340
+ }
6341
+ if (attempt < maxRetries - 1) {
6342
+ const delay = 100 * Math.pow(2, attempt);
6343
+ console.debug(
6344
+ `Retrying ${name} in ${delay}ms (attempt ${attempt + 2}/${maxRetries})`
6345
+ );
6346
+ await setTimeout2(delay);
6347
+ }
6348
+ }
6349
+ console.debug(`Failed to upload ${name}`);
6350
+ return { success: false };
6351
+ }
6313
6352
  get headers() {
6314
6353
  return {
6315
6354
  Authorization: `Bearer ${this.config.webUiKey}`
@@ -6326,32 +6365,28 @@ var Uploader = class {
6326
6365
  name
6327
6366
  }))
6328
6367
  );
6329
- const skippedFiles = [];
6368
+ const knowledge = await this.api.listKnowledgeFiles();
6369
+ this.knowledgeFilesCache = knowledge.files ?? [];
6370
+ await this.buildHashCache(this.knowledgeFilesCache);
6371
+ let skippedCount = 0;
6330
6372
  let successCount = 0;
6331
6373
  let failureCount = 0;
6332
- await this.api.listKnowledgeFiles();
6333
6374
  for (const file of files) {
6334
- let result = await this.uploadFile(file.name, file.contents);
6335
- while (!result.success && result.count && result.count < 5) {
6336
- console.debug("Failed to upload, retrying with count: ", result.count);
6337
- await setTimeout2(100);
6338
- result = await this.uploadFile(file.name, file.contents, result.count);
6339
- }
6375
+ const result = await this.uploadWithRetry(file.name, file.contents);
6340
6376
  if (result.skipped) {
6341
- skippedFiles.push(file.name);
6342
- }
6343
- if (result.success) {
6377
+ skippedCount++;
6378
+ } else if (result.success) {
6344
6379
  successCount++;
6345
6380
  } else {
6346
6381
  failureCount++;
6347
6382
  }
6348
6383
  }
6349
- if (skippedFiles.length > 0) {
6384
+ if (skippedCount > 0) {
6350
6385
  console.debug(
6351
- `Skipped uploading ${skippedFiles.length} files because their contents did not change`
6386
+ `Skipped uploading ${skippedCount} files because their contents did not change`
6352
6387
  );
6353
6388
  }
6354
- const uploadCount = successCount - skippedFiles.length;
6389
+ const uploadCount = Math.abs(successCount);
6355
6390
  if (uploadCount) {
6356
6391
  console.debug(`Successfully uploaded ${uploadCount} files`);
6357
6392
  }
@@ -6359,67 +6394,38 @@ var Uploader = class {
6359
6394
  console.debug(`Failed to upload ${failureCount} files`);
6360
6395
  }
6361
6396
  }
6362
- async uploadFile(name, contents, count = 0) {
6363
- const knowledge = await this.api.listKnowledgeFiles();
6364
- const knowledgeFile = (knowledge.files ?? []).find(
6365
- (f) => f.meta.name === name
6366
- );
6367
- if (knowledgeFile) {
6368
- console.debug("Found existing file:", knowledgeFile?.meta.name);
6369
- const data = await this.api.downloadFile(knowledgeFile.id);
6370
- if (!this.config.force && data) {
6371
- if (calculateFileHash(data) === calculateFileHash(contents)) {
6372
- return { skipped: true, success: true };
6373
- }
6374
- await mkdir3(resolve6(process.cwd(), `./temp/diff`), {
6375
- recursive: true
6376
- }).catch();
6377
- await writeFile4(
6378
- resolve6(process.cwd(), `./temp/diff/${name}-current.md`),
6379
- contents,
6380
- "utf-8"
6381
- );
6382
- await writeFile4(
6383
- resolve6(process.cwd(), `./temp/diff/${name}-owui.md`),
6384
- data,
6385
- "utf-8"
6386
- );
6387
- const dataLines = data.split("\n");
6388
- const contentLines = contents.split("\n");
6389
- if (dataLines.length === contentLines.length) {
6390
- const allLinesMatch = dataLines.every(
6391
- (line, i) => line === contentLines[i]
6392
- );
6393
- if (allLinesMatch) {
6394
- return { skipped: true, success: true };
6395
- }
6396
- }
6397
+ async uploadFile(name, contents) {
6398
+ const knowledgeFiles = this.knowledgeFilesCache ?? [];
6399
+ const knowledgeFile = knowledgeFiles.find((f) => f.meta.name === name);
6400
+ const contentHash = calculateFileHash(contents);
6401
+ if (knowledgeFile && !this.config.force) {
6402
+ const existingHash = this.fileHashCache.get(knowledgeFile.id);
6403
+ if (existingHash === contentHash) {
6404
+ return { skipped: true, success: true };
6397
6405
  }
6398
6406
  }
6399
- const fileBuffer = await readFile2(
6400
- resolve6(this.config.knowledgeFilePath, name)
6401
- );
6402
6407
  if (knowledgeFile) {
6403
6408
  await this.api.removeKnowledgeFile(knowledgeFile.id);
6404
- console.log(`Removed existing file: ${name}`);
6409
+ await this.waitForFileDeletion(knowledgeFile.id, name);
6405
6410
  }
6406
6411
  const spinner = ora(`Uploading ${name}`).start();
6412
+ const fileBuffer = await readFile2(
6413
+ resolve6(this.config.knowledgeFilePath, name)
6414
+ );
6407
6415
  const uploadResponse = await this.api.uploadFile(fileBuffer, name);
6408
6416
  if (!uploadResponse.id || !uploadResponse.filename) {
6409
- spinner.fail(`Error uploading ${name}, exiting`);
6410
- console.debug(uploadResponse);
6411
- return { success: false };
6417
+ spinner.fail(`Error uploading ${name}`);
6418
+ return { response: uploadResponse, success: false };
6412
6419
  }
6413
- await setTimeout2(500);
6414
6420
  spinner.text = `Associating ${name} with knowledge base`;
6415
6421
  const addResponse = await this.api.associateFile(uploadResponse.id);
6416
6422
  if (addResponse.name) {
6417
6423
  spinner.succeed(`${name} associated with knowledge base`);
6424
+ this.fileHashCache.set(uploadResponse.id, contentHash);
6418
6425
  return { success: true };
6419
6426
  } else {
6420
- spinner.fail(`Failed to associate ${name} with knowledge base`);
6421
- console.debug(addResponse);
6422
- return { count: count + 1, success: false };
6427
+ spinner.stop();
6428
+ return { response: addResponse, success: false };
6423
6429
  }
6424
6430
  }
6425
6431
  async uploadKnowledge() {
@@ -6459,7 +6465,7 @@ function addUploadKnowledgeCommand() {
6459
6465
  // src/react-demo-plugin/generate-lazy-demo-map.ts
6460
6466
  import { glob as glob3 } from "glob";
6461
6467
  import { uniqBy } from "lodash-es";
6462
- import { writeFile as writeFile5 } from "node:fs/promises";
6468
+ import { writeFile as writeFile4 } from "node:fs/promises";
6463
6469
  import { resolve as resolve8 } from "node:path";
6464
6470
  import { dedent as dedent2 } from "@qualcomm-ui/utils/dedent";
6465
6471
 
@@ -6528,7 +6534,7 @@ async function generateLazyDemoMap(options) {
6528
6534
  const demoPages = await scanForDemoPages({ routesDir });
6529
6535
  console.log(`Found ${demoPages.length} pages with demos`);
6530
6536
  const content = generateLazyDemoLoader(demoPages);
6531
- await writeFile5(outputPath, content, "utf-8");
6537
+ await writeFile4(outputPath, content, "utf-8");
6532
6538
  console.log(`
6533
6539
  Generated lazy demo loader at: ${outputPath}`);
6534
6540
  }