takaebato commented on code in PR #497:
URL: https://github.com/apache/echarts-doc/pull/497#discussion_r3035110059


##########
build/build-llms.js:
##########
@@ -0,0 +1,249 @@
+/**
+ * Converts built part JSONs (HTML desc) to Markdown using turndown,
+ * and generates llms.txt + individual .md files.
+ *
+ * Mechanically converts documents/*-parts/*.json to 
llms-documents/*-parts/*.md.
+ * Type information is extracted from documents/*.json (full schema) via 
traverse.
+ *
+ * Prerequisites: JSON must be built first (node build.js --env dev)
+ * Usage: node build/build-llms.js --env dev
+ */
+const fs = require('fs');
+const fse = require('fs-extra');
+const path = require('path');
+const globby = require('globby');
+const TurndownService = require('turndown');
+const {gfm} = require('turndown-plugin-gfm');
+const {traverse} = require('../tool/schemaHelper');
+const {readConfigEnvFile} = require('./helper');
+
+// --- Constants ---
+
+const LANGUAGES = ['en', 'zh'];
+const OUTPUT_DIR_NAME = 'llms-documents';
+const MAX_HEADING_DEPTH = 6;
+
+const CATEGORY_LABELS = {
+    en: {'option-parts': 'Option', 'option-gl-parts': 'Option GL', 
'api-parts': 'API', 'tutorial-parts': 'Tutorial'},
+    zh: {'option-parts': '配置项 (Option)', 'option-gl-parts': 'Option GL', 
'api-parts': 'API', 'tutorial-parts': '教程 (Tutorial)'}
+};
+
+const LLMS_TXT_HEADER = [
+    '# Apache ECharts Documentation',
+    '',
+    '> Apache ECharts is a free, powerful charting and visualization library 
offering easy ways to add intuitive, interactive, and highly customizable 
charts to your commercial products.',
+    ''
+].join('\n');
+
+// --- Config ---
+
+const argv = require('yargs').argv;
+const envType = (argv.dev != null || argv.debug != null || argv.env === 'dev') 
? 'dev' : argv.env;
+if (!envType) throw new Error('--env MUST be specified');
+const config = readConfigEnvFile(envType);
+
+// --- Turndown ---
+
+const td = new TurndownService({headingStyle: 'atx', codeBlockStyle: 
'fenced'});
+td.use(gfm);
+td.addRule('iframe', {filter: 'iframe', replacement: () => ''});
+
+function htmlToMd(html) {
+    return html ? td.turndown(html).replace(/\n{3,}/g, '\n\n').trim() : '';
+}
+
+// --- Extract type info from full schema JSON ---
+
+function buildTypeMap(schemaJsonPath, docName) {
+    if (!fs.existsSync(schemaJsonPath)) return {};
+    const schema = JSON.parse(fs.readFileSync(schemaJsonPath, 'utf-8'));
+    const typeMap = {};
+    traverse(schema, docName, (schemaPath, node) => {
+        if (node.type || node.default != null) {
+            typeMap[schemaPath] = {
+                type: node.type ? (Array.isArray(node.type) ? 
node.type.join('|') : node.type) : null,
+                default: node.default != null ? String(node.default) : null
+            };
+        }
+    });
+    return typeMap;
+}
+
+// --- Resolve links in HTML ---
+// Best-effort rewriting of <a href="#path"> and <a href="api.html#path"> in 
HTML
+// so that turndown produces markdown links pointing to the correct .md files.
+// Some source links have non-standard formats (e.g. missing "#", no dot 
separator)
+// that cannot be resolved; these are left as-is or linked to the orphan file.
+
+function tryResolveFileKey(linkPath, fileKeys) {
+    const [seg, ...rest] = linkPath.split('.');
+    const frag = rest.length > 0 ? rest.join('.') : null;
+    const segL = seg.toLowerCase();
+    const keysArr = [...fileKeys];
+
+    const key = fileKeys.has(seg)
+        ? seg
+        : keysArr.find(k => k.toLowerCase() === segL)
+            ?? keysArr.find(k => {
+                const kl = k.toLowerCase();
+                return kl === segL + 's' || kl + 's' === segL;
+            })
+            ?? null;
+
+    return key ? {key, frag} : null;
+}
+
+function tryResolveHtmlLinks(html, strippedKeys, docPrefix, hasOrphanFile) {
+    // Same-category links: href="#property.path" -> 
href="docPrefix.fileKey.md#fragment"
+    const resolved = html.replace(/href="#([^"]+)"/g, (match, lp) => {
+        const r = tryResolveFileKey(lp, strippedKeys);
+        if (!r) {
+            if (hasOrphanFile) return `href="${docPrefix}.md#${lp}"`;
+            return match;
+        }
+        return `href="${docPrefix}.${r.key}.md${r.frag ? '#' + r.frag : ''}"`;
+    });
+
+    // Cross-category links: href="api.html#echarts.init" -> 
href="../api-parts/api.echarts.md#init"
+    // Tutorial is a single file, so href="tutorial.html#X" -> 
href="../tutorial-parts/tutorial.md#X"
+    return resolved.replace(
+        /href="(option|api|tutorial)\.html#([^"]+)"/g,
+        (_, docType, fragment) => {
+            if (docType === 'tutorial') {
+                return `href="../tutorial-parts/tutorial.md#${fragment}"`;
+            }
+            const {key, frag} = tryResolveFileKey(fragment, new 
Set([fragment.split('.')[0]]));
+            return `href="../${docType}-parts/${docType}.${key}.md${frag ? '#' 
+ frag : ''}"`;
+        }

Review Comment:
   Fixed. Cross-doc links now resolve against actual file keys in the target 
directory, with fallback to the root file (e.g. `option.md#visualMap`) when no 
matching part file exists.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to