#!/usr/bin/env node 'use strict'; // 从官网 sitemap 生成 KB 类目地图 bridge/kb/11-site-map.md(旧 bridge site-map.md 的移植)。 // 只生成导航/类目页 Q/A,不包含商品详情页;生成后需重建 KB 索引。 const fs = require('fs'); const path = require('path'); const ROOT = path.resolve(__dirname, '..'); const OUT = path.join(ROOT, 'bridge', 'kb', '11-site-map.md'); const SITEMAP = process.env.SITE_MAP_URL || 'https://www.yehwang.com/sitemap.xml'; const BASE = process.env.SITE_BASE_URL || 'https://www.yehwang.com'; const WHITELIST = new Set(['jewelry', 'clothes', 'accessories', 'packing-and-display', 'bestsellers', 'new', 'top-deal', 'campaigns', 'brands', 'wholesale-service', 'factory-direct-wholesale-en', 'dropshipping', 'restock-request']); function titleCase(segment) { return String(segment || '') .replace(/-/g, ' ') .replace(/\b\w/g, (c) => c.toUpperCase()); } function buildEntries(urls) { const out = []; const seen = new Set(); for (const url of urls) { if (!url.startsWith(BASE)) continue; const pathPart = url.slice(BASE.length).split('?')[0]; if (!pathPart || pathPart === '/' || /\.html$/i.test(pathPart)) continue; const segments = pathPart.split('/').filter(Boolean); if (!segments.length || !WHITELIST.has(segments[0])) continue; if (segments.length > 4) continue; if (seen.has(pathPart)) continue; seen.add(pathPart); out.push({ path: pathPart, url: BASE + pathPart, name: titleCase(segments[segments.length - 1]) }); } out.sort((a, b) => a.path.localeCompare(b.path)); return out; } function render(entries) { const head = [ '---', 'title: Yehwang Website Category Map (Site Map)', 'category: site-map', `source: ${SITEMAP}`, 'generated: true', 'source_url:', ` - ${SITEMAP}`, `last_updated: ${new Date().toISOString().slice(0, 10)}`, '---', '', 'Every Yehwang category page has a direct URL. Use these links to send customers straight to the right page instead of a search query.', '', ]; const body = entries.map((e) => `### Q: Where can I find ${e.name}?\nBrowse our ${e.name} category: ${e.url}\n`); return head.join('\n') + '\n' + body.join('\n'); } (async () => { const res = await fetch(SITEMAP, { headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36' }, signal: AbortSignal.timeout(90000), }); if (!res.ok) throw new Error(`sitemap HTTP ${res.status}`); const xml = await res.text(); const urls = [...xml.matchAll(/([^<]+)<\/loc>/g)].map((m) => m[1].trim()); const entries = buildEntries(urls); if (!entries.length) throw new Error('generated 0 site-map entries'); fs.writeFileSync(OUT, render(entries), 'utf8'); console.log(JSON.stringify({ ts: new Date().toISOString(), site_map_kb: 'done', entries: entries.length, out: OUT })); })().catch((err) => { console.error('[build-site-kb]', err.message); process.exit(1); });