@qualcomm-ui/mdx-vite 2.5.1 → 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,121 +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) {
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 = page.url ?? `#${kebabCase(page.title)}`;
5779
- lines.push(`- [${page.title}](${url})`);
5780
- }
5781
- return lines.join("\n");
5782
- }
5783
- async function generateLlmsTxt(pages, projectName, description) {
5784
- const lines = [getIntroLines(pages, projectName, description)];
5785
- lines.push("");
5786
- for (const page of pages) {
5787
- const content = page.content.split("\n").map((line) => {
5788
- if (line.startsWith("#")) {
5789
- return `#${line}`;
5790
- }
5791
- return line;
5792
- });
5793
- if (content.every((line) => !line.trim())) {
5794
- continue;
5795
- }
5796
- lines.push(`## ${page.title}`);
5797
- lines.push("");
5798
- lines.push(content.join("\n"));
5799
5603
  lines.push("");
5604
+ lines.push(`> ${description}`);
5800
5605
  }
5801
5606
  return lines.join("\n");
5802
5607
  }
@@ -5827,44 +5632,11 @@ async function resolveModulePath(importPath, fromFile) {
5827
5632
  }
5828
5633
  return null;
5829
5634
  }
5830
- async function collectRelativeImports(filePath, visited = /* @__PURE__ */ new Set(), verbose) {
5831
- const normalizedPath = resolve5(filePath);
5832
- if (visited.has(normalizedPath)) {
5833
- return [];
5834
- }
5835
- visited.add(normalizedPath);
5836
- const modules = [];
5837
- try {
5838
- const content = await readFile(normalizedPath, "utf-8");
5839
- const relativeImports = extractRelativeImports(content);
5840
- for (const importPath of relativeImports) {
5841
- const resolvedPath = await resolveModulePath(importPath, normalizedPath);
5842
- if (!resolvedPath) {
5843
- if (verbose) {
5844
- console.log(
5845
- ` Could not resolve import: ${importPath} from ${normalizedPath}`
5846
- );
5847
- }
5848
- continue;
5849
- }
5850
- const importContent = await readFile(resolvedPath, "utf-8");
5851
- modules.push({
5852
- content: importContent,
5853
- path: resolvedPath
5854
- });
5855
- const nestedModules = await collectRelativeImports(
5856
- resolvedPath,
5857
- visited,
5858
- verbose
5859
- );
5860
- modules.push(...nestedModules);
5861
- }
5862
- } catch (error) {
5863
- if (verbose) {
5864
- console.log(` Error processing ${normalizedPath}: ${error}`);
5865
- }
5866
- }
5867
- return modules;
5635
+ function extractMetadata(metadata) {
5636
+ return (metadata ?? []).map((current) => {
5637
+ const [key, value] = current.split("=");
5638
+ return [key, value];
5639
+ });
5868
5640
  }
5869
5641
  var replaceNpmInstallTabs = () => {
5870
5642
  return (tree, _file, done) => {
@@ -5885,376 +5657,606 @@ var replaceNpmInstallTabs = () => {
5885
5657
  done();
5886
5658
  };
5887
5659
  };
5888
- function replaceTypeDocProps(docProps, verbose) {
5889
- return () => (tree, _file, done) => {
5890
- visit7(
5891
- tree,
5892
- "mdxJsxFlowElement",
5893
- (node, index, parent) => {
5894
- if (node?.name !== "TypeDocProps") {
5895
- 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
+ () => {
5896
5699
  }
5897
- const nameAttr = node.attributes?.find(
5898
- (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`
5899
5729
  );
5900
- const isPartial = node.attributes?.some(
5901
- (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
5902
5769
  );
5903
- if (!docProps || !nameAttr) {
5904
- if (parent && index !== void 0) {
5905
- parent.children.splice(index, 1);
5906
- }
5907
- 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"}`);
5908
5782
  }
5909
- const propsNames = extractNamesFromAttribute(nameAttr);
5910
- if (propsNames.length === 0) {
5911
- if (parent && index !== void 0) {
5912
- parent.children.splice(index, 1);
5913
- }
5914
- 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);
5915
5789
  }
5916
- const propsName = propsNames[0];
5917
- const componentProps = docProps.props[propsName];
5918
- if (!componentProps) {
5919
- if (verbose) {
5920
- console.log(` TypeDocProps not found: ${propsName}`);
5921
- }
5922
- if (parent && index !== void 0) {
5923
- 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
+ );
5924
5812
  }
5925
- return;
5926
- }
5927
- const propsDoc = extractProps(componentProps, Boolean(isPartial));
5928
- if (verbose) {
5929
- console.log(
5930
- ` Replaced TypeDocProps ${propsName} with API documentation`
5931
- );
5813
+ continue;
5932
5814
  }
5933
- Object.assign(node, {
5934
- lang: "json",
5935
- meta: null,
5936
- type: "code",
5937
- value: JSON.stringify(propsDoc, null, 2)
5815
+ const importContent = await readFile(resolvedPath, "utf-8");
5816
+ modules.push({
5817
+ content: importContent,
5818
+ path: resolvedPath
5938
5819
  });
5820
+ const nestedModules = await this.collectRelativeImports(
5821
+ resolvedPath,
5822
+ visited
5823
+ );
5824
+ modules.push(...nestedModules);
5939
5825
  }
5940
- );
5941
- done();
5942
- };
5943
- }
5944
- function replaceDemos(demosFolder, verbose, demoFiles) {
5945
- return () => async (tree) => {
5946
- const promises = [];
5947
- visit7(
5948
- tree,
5949
- "mdxJsxFlowElement",
5950
- (node, index, parent) => {
5951
- if (!node?.name || !["QdsDemo", "CodeDemo", "Demo"].includes(node.name)) {
5952
- 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
+ }
5953
5883
  }
5954
- const nameAttr = node.attributes?.find(
5955
- (attr) => attr.type === "mdxJsxAttribute" && attr.name === "name"
5956
- );
5957
- const nodeAttr = node.attributes?.find(
5958
- (attr) => attr.type === "mdxJsxAttribute" && attr.name === "node"
5959
- );
5960
- let demoName;
5961
- if (nameAttr && typeof nameAttr.value === "string") {
5962
- demoName = nameAttr.value;
5963
- } else if (nodeAttr?.value && typeof nodeAttr.value !== "string") {
5964
- const estree = nodeAttr.value.data?.estree;
5965
- if (estree?.body?.[0]?.type === "ExpressionStatement") {
5966
- const expression = estree.body[0].expression;
5967
- if (expression.type === "MemberExpression" && expression.object.type === "Identifier" && expression.object.name === "Demo" && expression.property.type === "Identifier") {
5968
- 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);
5969
5944
  }
5945
+ return;
5970
5946
  }
5971
- }
5972
- if (!demoName) {
5973
- if (parent && index !== void 0) {
5974
- 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;
5975
5953
  }
5976
- 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
+ });
5977
5977
  }
5978
- promises.push(
5979
- (async () => {
5980
- const kebabName = kebabCase(demoName);
5981
- let filePath = `${kebabName}.tsx`;
5982
- if (!demosFolder) {
5983
- if (verbose) {
5984
- console.log(` No demos folder for ${demoName}`);
5985
- }
5986
- if (parent && index !== void 0) {
5987
- 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;
5988
6011
  }
5989
- return;
5990
6012
  }
5991
- let demoFilePath = join3(demosFolder, filePath);
5992
- let isAngularDemo = false;
5993
- if (!await exists(demoFilePath)) {
5994
- demoFilePath = join3(demosFolder, `${kebabName}.ts`);
5995
- if (await exists(demoFilePath)) {
5996
- isAngularDemo = true;
5997
- filePath = `${kebabCase(demoName).replace("-component", ".component")}.ts`;
5998
- demoFilePath = join3(demosFolder, filePath);
5999
- } else {
6000
- 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
+ }
6001
6028
  if (parent && index !== void 0) {
6002
6029
  parent.children.splice(index, 1);
6003
6030
  }
6004
6031
  return;
6005
6032
  }
6006
- }
6007
- try {
6008
- const demoCode = await readFile(demoFilePath, "utf-8");
6009
- const cleanedCode = removePreviewLines(demoCode);
6010
- if (verbose) {
6011
- console.log(` Replaced demo ${demoName} with source code`);
6012
- }
6013
- demoFiles.push(demoFilePath);
6014
- Object.assign(node, {
6015
- lang: isAngularDemo ? "angular-ts" : "tsx",
6016
- meta: null,
6017
- type: "code",
6018
- value: cleanedCode
6019
- });
6020
- } catch (error) {
6021
- if (verbose) {
6022
- 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
+ }
6023
6048
  }
6024
- if (parent && index !== void 0) {
6025
- 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
+ }
6026
6069
  }
6027
- }
6028
- })()
6029
- );
6030
- }
6031
- );
6032
- await Promise.all(promises);
6033
- };
6034
- }
6035
- async function processMdxContent(mdxContent, pageUrl, demosFolder, docProps, verbose) {
6036
- const demoFiles = [];
6037
- let processedContent = mdxContent;
6038
- const lines = processedContent.split("\n");
6039
- const titleLine = lines.findIndex((line) => line.startsWith("# "));
6040
- processedContent = titleLine >= 0 ? lines.slice(titleLine + 1).join("\n") : processedContent;
6041
- 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;
6042
6087
  processedContent = processedContent.replace(
6043
6088
  /\[([^\]]+)\]\(\.\/#([^)]+)\)/g,
6044
- (_, text, anchor) => `[${text}](${pageUrl}#${anchor})`
6089
+ (_, text, anchor) => pageUrl && this.config.outputMode === "per-page" ? `[${text}](${pageUrl}#${anchor})` : text
6045
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 };
6046
6096
  }
6047
- const processor = unified4().use(remarkParse4).use(remarkMdx3).use(replaceTypeDocProps(docProps, verbose)).use(replaceDemos(demosFolder, verbose, demoFiles)).use(remarkStringify3);
6048
- const processed = await processor.process(processedContent);
6049
- processedContent = String(processed);
6050
- processedContent = processedContent.replace(/\n\s*\n\s*\n/g, "\n\n");
6051
- return { content: processedContent, demoFiles };
6052
- }
6053
- async function processComponent(component, docProps, verbose) {
6054
- try {
6055
- const mdxContent = await readFile(component.mdxFile, "utf-8");
6056
- if (verbose) {
6057
- 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;
6058
6134
  }
6059
- const processor = unified4().use(remarkParse4).use(remarkMdx3).use(replaceNpmInstallTabs).use(remarkFrontmatter3, ["yaml"]).use(remarkParseFrontmatter2).use(remarkSelfLinkHeadings(component.url)).use(remarkStringify3);
6060
- const parsed = await processor.process(mdxContent);
6061
- const frontmatter = parsed.data?.frontmatter || {};
6062
- const { content: processedContent, demoFiles } = await processMdxContent(
6063
- String(parsed),
6064
- component.url,
6065
- component.demosFolder,
6066
- docProps,
6067
- verbose
6068
- );
6069
- const removeJsxProcessor = unified4().use(remarkParse4).use(remarkMdx3).use(remarkRemoveJsx).use(remarkStringify3);
6070
- const removedJsx = String(
6071
- await removeJsxProcessor.process(processedContent)
6072
- );
6073
- const contentWithoutFrontmatter = removedJsx.replace(
6074
- /^---[\s\S]*?---\n/,
6075
- ""
6076
- );
6077
- const title = frontmatter.title || component.name;
6078
- return {
6079
- content: contentWithoutFrontmatter.trim(),
6080
- demoFiles,
6081
- frontmatter,
6082
- title,
6083
- url: component.url
6084
- };
6085
- } catch (error) {
6086
- console.error(`Error processing component ${component.name}:`, error);
6087
- throw error;
6088
6135
  }
6089
- }
6090
- async function generatePerPageExports({
6091
- includeImports,
6092
- metadata,
6093
- outputPath,
6094
- pages,
6095
- pageTitlePrefix,
6096
- processedPages,
6097
- verbose
6098
- }) {
6099
- await mkdir2(dirname(outputPath), { recursive: true }).catch();
6100
- const count = processedPages.length;
6101
- let totalSize = 0;
6102
- await Promise.all(
6103
- processedPages.map(async (processedPage, index) => {
6104
- const page = pages[index];
6105
- const lines = [];
6106
- if (metadata.length || page.url) {
6107
- lines.push("---");
6108
- if (page.url) {
6109
- lines.push(`url: ${page.url}`);
6110
- }
6111
- if (metadata.length) {
6112
- for (const [key, value] of metadata) {
6113
- lines.push(`${key}: ${value}`);
6114
- }
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}`;
6115
6145
  }
6116
- lines.push("---");
6117
- lines.push("");
6146
+ return line;
6147
+ });
6148
+ if (content.every((line) => !line.trim())) {
6149
+ continue;
6118
6150
  }
6119
- lines.push(`# ${processedPage.title}`);
6151
+ lines.push(`## ${page.title}`);
6120
6152
  lines.push("");
6121
- if (processedPage.frontmatter?.title) {
6122
- page.name = processedPage.frontmatter.title;
6123
- }
6124
- let content = processedPage.content;
6125
- if (pageTitlePrefix) {
6126
- content = content.replace(
6127
- `# ${page.name}`,
6128
- `# ${pageTitlePrefix} ${page.name}`
6129
- );
6130
- page.name = `${pageTitlePrefix} ${page.name}`;
6131
- }
6132
- lines.push(content);
6153
+ lines.push(content.join("\n"));
6133
6154
  lines.push("");
6134
- if (includeImports && processedPage.demoFiles.length > 0) {
6135
- if (verbose) {
6136
- console.log(
6137
- `Collecting imports for ${page.name} from ${processedPage.demoFiles.length} demo files`
6138
- );
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("");
6139
6195
  }
6140
- const allImports = [];
6141
- for (const demoFile of processedPage.demoFiles) {
6142
- const imports = await collectRelativeImports(
6143
- demoFile,
6144
- /* @__PURE__ */ new Set(),
6145
- 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}`
6146
6206
  );
6147
- allImports.push(...imports);
6207
+ page.name = `${this.config.pageTitlePrefix} ${page.name}`;
6148
6208
  }
6149
- const uniqueImports = Array.from(
6150
- new Map(allImports.map((m) => [m.path, m])).values()
6151
- );
6152
- if (verbose) {
6153
- console.log(
6154
- ` 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()
6155
6227
  );
6156
- }
6157
- if (uniqueImports.length > 0) {
6158
- lines.push("## Related Source Files");
6159
- lines.push("");
6160
- for (const importedModule of uniqueImports) {
6161
- const ext = extname(importedModule.path).slice(1);
6162
- lines.push(`### ${basename(importedModule.path)}`);
6163
- lines.push("");
6164
- lines.push(`\`\`\`${ext}`);
6165
- lines.push(importedModule.content);
6166
- 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");
6167
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
+ }
6168
6245
  }
6169
6246
  }
6170
- }
6171
- const outfile = `${resolve5(outputPath)}/${kebabCase(page.id || page.name)}.md`;
6172
- await writeFile3(outfile, lines.join("\n"), "utf-8");
6173
- const stats = await stat(outfile);
6174
- totalSize += stats.size / 1024;
6175
- })
6176
- );
6177
- console.log(`Generated ${count} component(s) in ${outputPath}`);
6178
- console.log(`Folder size: ${totalSize.toFixed(1)} KB`);
6179
- }
6180
- function extractMetadata(metadata) {
6181
- return (metadata ?? []).map((current) => {
6182
- const [key, value] = current.split("=");
6183
- return [key, value];
6184
- });
6185
- }
6186
- async function generate({
6187
- baseUrl,
6188
- clean,
6189
- description,
6190
- docPropsPath,
6191
- exclude,
6192
- includeImports,
6193
- metadata,
6194
- name,
6195
- outputMode,
6196
- outputPath,
6197
- pageTitlePrefix,
6198
- routeDir,
6199
- verbose
6200
- }) {
6201
- const extractedMetadata = extractMetadata(metadata);
6202
- if (verbose) {
6203
- console.log(`Scanning pages in: ${routeDir}`);
6204
- if (exclude?.length) {
6205
- console.log(`Excluding patterns: ${exclude.join(", ")}`);
6206
- }
6207
- }
6208
- const [docProps, pages] = await Promise.all([
6209
- loadDocProps(routeDir, docPropsPath, verbose),
6210
- scanPages(routeDir, verbose, exclude, baseUrl)
6211
- ]);
6212
- if (pages.length === 0) {
6213
- console.log("No pages found.");
6214
- return;
6215
- }
6216
- if (verbose) {
6217
- console.log(`Found ${pages.length} page(s)`);
6218
- }
6219
- const processedPages = [];
6220
- for (const page of pages) {
6221
- try {
6222
- const processed = await processComponent(page, docProps, verbose);
6223
- processedPages.push(processed);
6224
- } catch (error) {
6225
- console.error(`Failed to process page: ${page.name}`);
6226
- process.exit(1);
6227
- }
6228
- }
6229
- if (clean) {
6230
- await rm(outputPath, { force: true, recursive: true }).catch();
6231
- }
6232
- if (outputMode === "aggregated") {
6233
- const llmsTxtContent = await generateLlmsTxt(
6234
- processedPages,
6235
- name,
6236
- description
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
+ })
6237
6252
  );
6238
- await mkdir2(dirname(outputPath), { recursive: true }).catch();
6239
- await writeFile3(outputPath, llmsTxtContent, "utf-8");
6240
- const outputStats = await stat(outputPath);
6241
- const outputSizeKb = (outputStats.size / 1024).toFixed(1);
6242
- console.log(
6243
- `Generated ${outputPath} with ${pages.length} component(s) at: ${outputPath}`
6244
- );
6245
- console.log(`File size: ${outputSizeKb} KB`);
6246
- } else {
6247
- await mkdir2(outputPath, { recursive: true }).catch();
6248
- await generatePerPageExports({
6249
- includeImports,
6250
- metadata: extractedMetadata,
6251
- outputPath,
6252
- pages,
6253
- pageTitlePrefix,
6254
- processedPages,
6255
- verbose
6256
- });
6253
+ console.log(`Generated ${count} component(s) in ${this.config.outputPath}`);
6254
+ console.log(`Folder size: ${totalSize.toFixed(1)} KB`);
6257
6255
  }
6256
+ };
6257
+ async function generate(config2) {
6258
+ const generator = new KnowledgeGenerator(config2);
6259
+ await generator.run();
6258
6260
  }
6259
6261
  function addGenerateKnowledgeCommand() {
6260
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(
@@ -6276,14 +6278,7 @@ function addGenerateKnowledgeCommand() {
6276
6278
 
6277
6279
  // src/open-web-ui-knowledge/upload-knowledge.ts
6278
6280
  import { createHash as createHash2 } from "node:crypto";
6279
- import {
6280
- access as access2,
6281
- mkdir as mkdir3,
6282
- readdir as readdir2,
6283
- readFile as readFile2,
6284
- stat as stat2,
6285
- writeFile as writeFile4
6286
- } from "node:fs/promises";
6281
+ import { access as access2, readdir as readdir2, readFile as readFile2, stat as stat2 } from "node:fs/promises";
6287
6282
  import { resolve as resolve6 } from "node:path";
6288
6283
  import { setTimeout as setTimeout2 } from "node:timers/promises";
6289
6284
  import ora from "ora";
@@ -6294,10 +6289,66 @@ function calculateFileHash(fileData) {
6294
6289
  var Uploader = class {
6295
6290
  config;
6296
6291
  api;
6292
+ fileHashCache = /* @__PURE__ */ new Map();
6293
+ knowledgeFilesCache = null;
6297
6294
  constructor(config2) {
6298
6295
  this.config = config2;
6299
6296
  this.api = new KnowledgeApi(config2);
6300
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
+ }
6301
6352
  get headers() {
6302
6353
  return {
6303
6354
  Authorization: `Bearer ${this.config.webUiKey}`
@@ -6314,32 +6365,28 @@ var Uploader = class {
6314
6365
  name
6315
6366
  }))
6316
6367
  );
6317
- const skippedFiles = [];
6368
+ const knowledge = await this.api.listKnowledgeFiles();
6369
+ this.knowledgeFilesCache = knowledge.files ?? [];
6370
+ await this.buildHashCache(this.knowledgeFilesCache);
6371
+ let skippedCount = 0;
6318
6372
  let successCount = 0;
6319
6373
  let failureCount = 0;
6320
- await this.api.listKnowledgeFiles();
6321
6374
  for (const file of files) {
6322
- let result = await this.uploadFile(file.name, file.contents);
6323
- while (!result.success && result.count && result.count < 5) {
6324
- console.debug("Failed to upload, retrying with count: ", result.count);
6325
- await setTimeout2(100);
6326
- result = await this.uploadFile(file.name, file.contents, result.count);
6327
- }
6375
+ const result = await this.uploadWithRetry(file.name, file.contents);
6328
6376
  if (result.skipped) {
6329
- skippedFiles.push(file.name);
6330
- }
6331
- if (result.success) {
6377
+ skippedCount++;
6378
+ } else if (result.success) {
6332
6379
  successCount++;
6333
6380
  } else {
6334
6381
  failureCount++;
6335
6382
  }
6336
6383
  }
6337
- if (skippedFiles.length > 0) {
6384
+ if (skippedCount > 0) {
6338
6385
  console.debug(
6339
- `Skipped uploading ${skippedFiles.length} files because their contents did not change`
6386
+ `Skipped uploading ${skippedCount} files because their contents did not change`
6340
6387
  );
6341
6388
  }
6342
- const uploadCount = successCount - skippedFiles.length;
6389
+ const uploadCount = Math.abs(successCount);
6343
6390
  if (uploadCount) {
6344
6391
  console.debug(`Successfully uploaded ${uploadCount} files`);
6345
6392
  }
@@ -6347,67 +6394,38 @@ var Uploader = class {
6347
6394
  console.debug(`Failed to upload ${failureCount} files`);
6348
6395
  }
6349
6396
  }
6350
- async uploadFile(name, contents, count = 0) {
6351
- const knowledge = await this.api.listKnowledgeFiles();
6352
- const knowledgeFile = (knowledge.files ?? []).find(
6353
- (f) => f.meta.name === name
6354
- );
6355
- if (knowledgeFile) {
6356
- console.debug("Found existing file:", knowledgeFile?.meta.name);
6357
- const data = await this.api.downloadFile(knowledgeFile.id);
6358
- if (!this.config.force && data) {
6359
- if (calculateFileHash(data) === calculateFileHash(contents)) {
6360
- return { skipped: true, success: true };
6361
- }
6362
- await mkdir3(resolve6(process.cwd(), `./temp/diff`), {
6363
- recursive: true
6364
- }).catch();
6365
- await writeFile4(
6366
- resolve6(process.cwd(), `./temp/diff/${name}-current.md`),
6367
- contents,
6368
- "utf-8"
6369
- );
6370
- await writeFile4(
6371
- resolve6(process.cwd(), `./temp/diff/${name}-owui.md`),
6372
- data,
6373
- "utf-8"
6374
- );
6375
- const dataLines = data.split("\n");
6376
- const contentLines = contents.split("\n");
6377
- if (dataLines.length === contentLines.length) {
6378
- const allLinesMatch = dataLines.every(
6379
- (line, i) => line === contentLines[i]
6380
- );
6381
- if (allLinesMatch) {
6382
- return { skipped: true, success: true };
6383
- }
6384
- }
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 };
6385
6405
  }
6386
6406
  }
6387
- const fileBuffer = await readFile2(
6388
- resolve6(this.config.knowledgeFilePath, name)
6389
- );
6390
6407
  if (knowledgeFile) {
6391
6408
  await this.api.removeKnowledgeFile(knowledgeFile.id);
6392
- console.log(`Removed existing file: ${name}`);
6409
+ await this.waitForFileDeletion(knowledgeFile.id, name);
6393
6410
  }
6394
6411
  const spinner = ora(`Uploading ${name}`).start();
6412
+ const fileBuffer = await readFile2(
6413
+ resolve6(this.config.knowledgeFilePath, name)
6414
+ );
6395
6415
  const uploadResponse = await this.api.uploadFile(fileBuffer, name);
6396
6416
  if (!uploadResponse.id || !uploadResponse.filename) {
6397
- spinner.fail(`Error uploading ${name}, exiting`);
6398
- console.debug(uploadResponse);
6399
- return { success: false };
6417
+ spinner.fail(`Error uploading ${name}`);
6418
+ return { response: uploadResponse, success: false };
6400
6419
  }
6401
- await setTimeout2(500);
6402
6420
  spinner.text = `Associating ${name} with knowledge base`;
6403
6421
  const addResponse = await this.api.associateFile(uploadResponse.id);
6404
6422
  if (addResponse.name) {
6405
6423
  spinner.succeed(`${name} associated with knowledge base`);
6424
+ this.fileHashCache.set(uploadResponse.id, contentHash);
6406
6425
  return { success: true };
6407
6426
  } else {
6408
- spinner.fail(`Failed to associate ${name} with knowledge base`);
6409
- console.debug(addResponse);
6410
- return { count: count + 1, success: false };
6427
+ spinner.stop();
6428
+ return { response: addResponse, success: false };
6411
6429
  }
6412
6430
  }
6413
6431
  async uploadKnowledge() {
@@ -6447,7 +6465,7 @@ function addUploadKnowledgeCommand() {
6447
6465
  // src/react-demo-plugin/generate-lazy-demo-map.ts
6448
6466
  import { glob as glob3 } from "glob";
6449
6467
  import { uniqBy } from "lodash-es";
6450
- import { writeFile as writeFile5 } from "node:fs/promises";
6468
+ import { writeFile as writeFile4 } from "node:fs/promises";
6451
6469
  import { resolve as resolve8 } from "node:path";
6452
6470
  import { dedent as dedent2 } from "@qualcomm-ui/utils/dedent";
6453
6471
 
@@ -6516,7 +6534,7 @@ async function generateLazyDemoMap(options) {
6516
6534
  const demoPages = await scanForDemoPages({ routesDir });
6517
6535
  console.log(`Found ${demoPages.length} pages with demos`);
6518
6536
  const content = generateLazyDemoLoader(demoPages);
6519
- await writeFile5(outputPath, content, "utf-8");
6537
+ await writeFile4(outputPath, content, "utf-8");
6520
6538
  console.log(`
6521
6539
  Generated lazy demo loader at: ${outputPath}`);
6522
6540
  }